Publi

Crear mapas HTML5 interactivos con RaphaelJS


Vivimos en pleno auge del HTML5 y es que con esta tecnología se pueden hacer cosas muy interesantes, de forma muy rápida, elegante y multiplataforma sin necesidad de recurrir a herramientas de terceros como Flash, y eso está muy bien. Personalmente nunca me gustó Flash, así que, tenemos muchas cosas chulas por hacer.

Hace unos días, para un pequeño proyecto surgió la idea de crear un mapa interactivo de municipios, la idea está bien y hay mucho trabajo que hacer. Pero por un lado, tenemos cierta información ya disponible, por ejemplo los mapas y los nombres de los municipios los podemos encontrar por Internet fácilmente. Los mapas los podemos encontrar por ejemplo en esta web. En realidad es una recopilación de mapas de la Wikipedia (he copiado la recopilación aquí, por si la web citada deja de funcionar.)

En este pequeño tutorial voy a hacer un mapa interactivo de los municipios de Málaga en el que se podrá pasar el ratón por encima de los municipios y hacer click en cada uno de ellos.

Para facilitarme la vida he utilizado la biblioteca Javascript RaphaëlJS, con la que puedo dibujar trayectorias SVG de forma muy fácil y además como cada trayectoria será un objeto podré asignarles eventos a través de jQuery. Se va a quedar muy chulo.

De todas formas, para este tutorial utilizaré este mapa de municipios de Málaga, sacado de la Wikipedia, hecha por Emilio Gómez Fernández:

Iniciándonos con RaphaelJS

La gracia está en que el formato en el que maneja RaphaelJS las trayectorias es el mismo que se usa para los archivos SVG. Al final los archivos SGV son archivos XML con información sobre las trayectorias y estilos dentro.

Para hacer algo sencillo con RaphaelJS vamos a crear un archivo HTML extremadamente sencillo, con lo siguiente:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
  <head>
    <title>Ejemplo Raphaeljs</title>
    <script type="text/javascript" src="raphael-min.js"></script>
  </head>

  <body>
    <h1>Ejemplo Raphaeljs</h1>
    <hr />
    <div id="lienzo">
    </div>
    <script>
      var rjs = Raphael('lienzo', 600, 400);

      rjs.path('m 95.725713,145.10504 0,-11.088 9.359997,0 0,-9.36 39.456,0 0,9.36 9.504,0 0,19.008 9.504,0 0,39.456 -9.504,0 0,9.36 -20.304,0 0,-10.8 9.216,0 0,-36.576 -9.36,0 0,-9.36 -7.92,0 0,94.896 -9.504,0 0,9.36 -11.088,0 0,-104.256 -9.359997,0');
      rjs.path('m 172.59086,293.9256 0,-94.896 -9.36,0 0,-11.088 9.36,0 0,-9.36 39.456,0 0,9.36 9.504,0 0,19.008 9.504,0 0,29.952 -9.36,0 0,8.064 9.36,0 0,39.456 -9.504,0 0,9.504 -9.504,0 0,9.36 -29.952,0 0,-9.36 -9.504,0 m 20.592,-11.088 17.28,0 0,-36.432 -9.216,0 0,-10.944 9.216,0 0,-27.072 -9.36,0 0,-9.36 -7.92,0 0,83.808');

    </script>
  </body>
</html>

Las dos trayectorias utilizadas, incluidas en rjs.path() están sacadas de un SVG y el resultado sería algo así:

Efecto hover

Vamos a añadir un efecto a cada uno de los paths o trayectorias del ejemplo anterior. Por ahora, todo sigue muy artesano, pero nos da una pista de cómo podemos aplicar el efecto a cada uno de los elementos del mapa:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
  <head>
    <title>Ejemplo Raphaeljs</title>
    <script type="text/javascript" src="raphael-min.js"></script>
  </head>

  <body>
    <h1>Ejemplo Raphaeljs</h1>
    <hr />
    <div id="lienzo">
    </div>
    <script>
      var default_attributes = {
            fill: '#abcabc',
            stroke: '#000000',
            'stroke-width': 4,
        };
      var rjs = Raphael('lienzo', 600, 400);

      var p1 = rjs.path('m 95.725713,145.10504 0,-11.088 9.359997,0 0,-9.36 39.456,0 0,9.36 9.504,0 0,19.008 9.504,0 0,39.456 -9.504,0 0,9.36 -20.304,0 0,-10.8 9.216,0 0,-36.576 -9.36,0 0,-9.36 -7.92,0 0,94.896 -9.504,0 0,9.36 -11.088,0 0,-104.256 -9.359997,0');
      p1.attr(default_attributes);
      p1.hover(function() {
        this.animate({
      fill: '#00bbff'
    }, 100);
    p1[0].style.cursor = 'crosshair';
      },
      function() {
        this.animate({
      fill: default_attributes.fill
    }, 100);
      }).click(function() {
        alert("click en la P");
      });;
      var p2 = rjs.path('m 172.59086,293.9256 0,-94.896 -9.36,0 0,-11.088 9.36,0 0,-9.36 39.456,0 0,9.36 9.504,0 0,19.008 9.504,0 0,29.952 -9.36,0 0,8.064 9.36,0 0,39.456 -9.504,0 0,9.504 -9.504,0 0,9.36 -29.952,0 0,-9.36 -9.504,0 m 20.592,-11.088 17.28,0 0,-36.432 -9.216,0 0,-10.944 9.216,0 0,-27.072 -9.36,0 0,-9.36 -7.92,0 0,83.808');
      p2.attr(default_attributes);
      p2.hover(function() {
        this.animate({
      fill: '#ffbb00'
    }, 100);
    p2[0].style.cursor = 'wait';
      },
      function() {
        this.animate({
      fill: default_attributes.fill
    }, 100);
      }).click(function() {
        alert("click en la B");
      });;

    </script>
  </body>
</html>

El resultado es algo así:

Creando un mapa

En este ejemplo ya estamos cambiando cursores y colores. Ahora bien, hacer todo esto con tantas trayectorias puedes ser un poco más complicado, podemos hacerlo de varias formas. Algunas de ellas implican un poco de programación en el servidor (aunque no voy a entrar ahí hoy), lo que sí deberíamos hacer es establecer una correspondencia entre los identificadores de las trayectorias que encontramos en el SVG (sólo tenemos que abrirlo) con los nombres o la identificación de los elementos del mapa. Para lo cual, podríamos utilizar una base de datos.

Tenemos que tener en cuenta que si abrimos el archivo SVG con un programa y lo guardamos de nuevo, puede que algunos IDs cambien por lo que podremos perder información.

Hemos visto en la imagen de ejemplo, que el SVG tiene los nombres escritos (lamentáblemente, si abrimos este SVG, no encontramos el texto del nombre, sino las trayectorias que forman cada letra, por lo que sería muy complicado establecer las correspondencias automáticamente). De todas formas, los textos no nos hacen falta ahora mismo, por lo que los voy a eliminar (desde Javascript). En principio, vamos a mostrar el id de las trayectorias, por lo que no haremos correspondencias entre trayectoriasnombres.

En este ejemplo, he utilizado jQuery para hacer una descarga ajax del archivo SVG y cargarlo, así como buscar dentro de la estructura XML del SVG y escribir el nombre del ID:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
  <head>
    <title>Ejemplo Raphaeljs</title>
    <script type="text/javascript" src="jquery.min.js"></script>
    <script type="text/javascript" src="raphael-min.js"></script>
  </head>

  <body>
    <h1>Ejemplo Raphaeljs</h1>
    <hr />
    <div id="municipiotxt">Selecciona un municipio</div>
    <div id="lienzo">
      <img id="loadingicon" src="loading.gif" />
    </div>
    <script>
      var default_attributes = {
            fill: '#abcabc',
            stroke: '#000000',
            'stroke-width': 1,
        };
      var $munictxt = $('#municipiotxt');

      $.ajax({
        url: 'Malaga_municipios.svg',
    type: 'GET',
    dataType: 'xml',
    success: function(xml) {
      var rjs = Raphael('lienzo', 700, 400);

      $(xml).find('svg > g > g > path').each(function() {
        var path = $(this).attr('d');
        var pid = $(this).attr('id');
        var munic = rjs.path(path);
        munic.attr(default_attributes);
        munic.hover(function() {
          this.animate({ fill: '#00bbff' });
          $munictxt.html("Municipio: "+pid);
        }, function() {
          this.animate({ fill: default_attributes.fill });
          $munictxt.html("Selecciona un municipio");
        }). click(function() {
          alert("Click sobre un municipio. ID = "+pid);
        });
          });
      $('#loadingicon').hide();
    }
      });

    </script>
  </body>
</html>

Para finalizar, vamos a hacer corresponder algunos ID de trayectoria con el municipio que representan (muy parecido al anterior):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
  <head>
    <title>Ejemplo Raphaeljs</title>
    <script type="text/javascript" src="jquery.min.js"></script>
    <script type="text/javascript" src="raphael-min.js"></script>
  </head>

  <body>
    <h1>Ejemplo Raphaeljs</h1>
    <hr />
    <div id="municipiotxt">Selecciona un municipio</div>
    <div id="lienzo">
      <img id="loadingicon" src="loading.gif" />
    </div>
    <script>
      var municipios_data = {'path2643': 'Cuevas de San Marcos',
        'path2647': 'Alameda',
    'path2651': 'Cuevas Hajas',
    'path2655': 'Villanueva de Algaidas',
    'path2659': 'Antequera',
    'path2667': 'Villanueva de Tapia',
    'path2671': 'Sierra de Yeguas',
    'path2675': 'Mollina',
    'path2679': 'Humilladero',
    'path2683': 'Fuente de Piedra',
    'path2687': 'Archidona',
    'path2691': 'Campillos',
    'path2695': 'Villanueva del Trabuco',
    'path2699': 'Almargen',
    'path2703': 'Teba',
    'path2707': 'Villanueva del Rosario',
    'path2711': 'Cañete la Real',
    'path2715': 'Alfarnate',
    'path2719': 'Alfarnatejo',
    'path2723': 'Colmenar',
    'path2727': 'Periana',
    'path2731': 'Riogordo',
    'path2735': 'Valle de Abdalajis',
    'path2739': 'Alcaucín',
    'path2743': 'Ardales',
    'path2747': 'Álora',
    'path2751': 'Ronda',
    'path2763': 'Cuevas del Becerro',
    'path2775': 'Canillas de Aceituno',
    'path2779': 'Sedella',
    'path2787': 'Viñuela',
    'path2795': 'Málaga',
    'path2799': 'El Burgo',
    'path2803': 'Canillas de Albaida',
    'path2807': 'Salares',
    'path2811': 'Comares',
    'path2819': 'Carratraca',
    'path2823': 'Cómpeta',
    'path2827': 'Casarabonela',
    'path2831': 'Vélez-Málaga',
    'path2839': 'Frigiliana',
    'path2843': 'Nerja',
    'path2859': 'Arriate',
    'path2867': 'Cártama',
    'path2871': 'Torrox',
    'path2875': 'Pizarra',
    'path2899': 'Algarrobo',
    'path2903': 'Yunquera',
    'path2907': 'Montejaque',
    'path2911': 'Totalán',
    'path2915': 'Alozaina',
    'path2927': 'Coín',
    'path2931': 'Benaoján',
    'path2935': 'Rincón de la Victoria',
    'path2939': 'Tolox',
    'path2943': 'Alhaurín de la Torre',
    'path2947': 'Guaro',
    'path2951': 'Alpandeite',
    'path2955': 'Alhaurín el Grande',
    'path2959': 'Parauta',
    'path2967': 'Jimera de Líbar',
    'path2971': 'Cortes de la Frontera',
    'path2975': 'Júzcar',
    'path2979': 'Atajate',
    'path2983': 'Igualeja',
    'path2987': 'Istán',
    'path2991': 'Monda',
    'path2995': 'Faraján',
    'path2999': 'Torremolinos',
    'path3003': 'Benahavís',
    'path3007': 'Benadalid',
    'path3011': 'Pujarra',
    'path3015': 'Mijas',
    'path3019': 'Benalmádena',
    'path3023': 'Benalauría',
    'path3027': 'Ojén',
    'path3031': 'Jubrique',
    'path3035': 'Algatocín',
    'path3039': 'Fuengirola',
    'path3043': 'Benarrabá',
    'path3047': 'Genalguacíl',
    'path3051': 'Gaucín',
    'path3055': 'Marbella',
    'path3059': 'Estepona',
    'path3063': 'Casares',
    'path3067': 'Manilva'};

      var default_attributes = {
            fill: '#abcabc',
            stroke: '#000000',
            'stroke-width': 1,
        };
      var $munictxt = $('#municipiotxt');

      $.ajax({
        url: 'Malaga_municipios.svg',
    type: 'GET',
    dataType: 'xml',
    success: function(xml) {
      var rjs = Raphael('lienzo', 700, 400);
      var corr="";
      $(xml).find('svg > g > g > path').each(function() {
        var path = $(this).attr('d');
    var pid = $(this).attr('id');
        var munic = rjs.path(path);

        munic.attr(default_attributes);
        munic.hover(function() {
          this.animate({ fill: '#00bbff' });
          var text = "Municipio: ";
          if (typeof(municipios_data[pid])!='undefined')
            text+=municipios_data[pid];
          else
            text+="Sin nombre";
          text+="("+$(this).attr('id')+")";

          $munictxt.html(text);
        }, function() {
          this.animate({ fill: default_attributes.fill });
          $munictxt.html("Selecciona un municipio");
        }). click(function() {
          alert("Click sobre un municipio. ID = "+$(this).attr('id'));
        });
          });
      $('#loadingicon').hide();
    }
      });

    </script>
  </body>
</html>

El resultado de todo esto será:

Selecciona municipio

Otra opción, sería almacenar las trayectorias individualmente en base de datos con los datos asociados que queramos almacenar (población, usuarios de esas zonas, enlaces y más), descargarlo por AJAX cuando se genera la página o devolver las trayectorias y los datos adicionales junto con el HTML de la página.

Actualización 15/07/2016: Cambio de la web oficial de RaphaelJs, el dominio que había antes dejó de funcionar. Ahora enlazo al GitHub. Gracias Dann!

También podría interesarte...

There are 22 comments left Ir a comentario

  1. GustavoMG /
    Usando Mozilla Firefox Mozilla Firefox 35.0 en Windows Windows 7

    Hola, Muy buen post! Disculpa los archivos del jquery.min.js no pusiste un enlace para descargar.
    Saludos y gracias

    1. Gaspar Fernández / Post Author
      Usando Google Chrome Google Chrome 39.0.2166.2 en Linux Linux

      Para descargar jquery puedes ir a: http://jquery.com/download/

      Muchas gracias por tu comentario

  2. Emilio /
    Usando Google Chrome Google Chrome 43.0.2357.134 en Windows Windows XP

    He implementado el ejemplo en un servidor IIS 7.0, añadiendo el tipo SVG, pero no me muestra el mapa. En cambio, las letras si las muestra. ¿Que puede estar pasando?

    Muchas Gracias!

    1. Gaspar Fernández / Post Author
      Usando Mozilla Firefox Mozilla Firefox 39.0 en Ubuntu Linux Ubuntu Linux

      ¿El fichero del mapa se encuentra accesible? Es decir, directamente desde el navegador podrías descargarte el mapa en la ruta que le especificamos a jquery ?

      1. Emilio /
        Usando Google Chrome Google Chrome 43.0.2357.134 en Windows Windows XP

        Si, está disponible la ruta de la imagen. Al final conseguí arreglarlo, cambié en la funcion find la ruta de busqueda del path.

        $(xml).find(‘svg > g > g > path’).each(function() { …}

        por este

        $(xml).find(‘svg > g > path’).each(function() { …}

        y ya me lo muestra, lo que si es que no reconoce el nombre de los municipios que están dentro de la imagen.

        1. Gaspar Fernández / Post Author
          Usando Mozilla Firefox Mozilla Firefox 39.0 en Ubuntu Linux Ubuntu Linux

          Hola Emilio. Intenta visualizar los elementos dentro del $.each() con console.log() a ver si así te puede dar una pista de lo que pasa. A priori, creo que no te pilla bien los IDs de los elementos. Igual que tuviste que cambiar el path

  3. Fabian Hernandez /
    Usando Google Chrome Google Chrome 45.0.2454.85 en Windows Windows 7

    Hola realmente no se nada de sitios web o algo similar pero si de mapas y deseo con muchas ganas poder llegar a hacer algo asi por eso pongo mi consulta por mas tonta y básica que parezca xq realmente quiero aprender y espero alguien me ayude.

    Abri una pagina de google normalmente y le di inspeccionar, luego ahi dentro borre la informacion que tenia en “elements” y copie lo que decia la ultima pagina de color negro.. pero en la parte de la izquierda donde se deberia visualizar mi avance solo dice “EJEMPLO RAPHAELJS” y “selecciona un municipio” pero no los graficos de distritos…. ahora bien descargue el archivo jquery.min.js pero la verdad no se donde utilizarlo.

    Perdon ser basico en esto pero si tengo muchas ganas de aprender… asi que si alguien puede ayudarme le daria las gracias de antemano.

    Saludos

    1. Gaspar Fernández / Post Author
      Usando Mozilla Firefox Mozilla Firefox 40.0 en Ubuntu Linux Ubuntu Linux

      Hola Fabian,

      Intenta copiar el ejemplo tal cual, y crear un archivo html desde cero. Muchas veces cuando borramos cosas desde el inspector que no debemos y cuando hay scripts como este implicados es mucho más fácil cargarnos algo. Sobre todo porque bibliotecas como raphael.js generan contenidos, y no se van a dibujar cosas nuevas.

      Por eso, si creamos un archivo html desde cero (bájate jquery.min.js, raphael-min.js, inclúyelos como hago en el ejemplo) y carga el archivo html con el navegador a ver si así funciona. Dese ahí podrás modificar el html que has creado y recargar en el navegador las veces que quieras hasta conseguir el resultado deseado.

  4. luis /
    Usando Google Chrome Google Chrome 48.0.2564.116 en Windows Windows 8

    No me carga la url: ‘La_Coruña.svg’, pegue el archivo La_Coruña.svg dentro de la carpeta de donde esta mi .html, pero no me carga.

    1. Gaspar Fernández / Post Author
      Usando Mozilla Firefox Mozilla Firefox 44.0 en Ubuntu Linux Ubuntu Linux

      Hola Luis,

      Tal vez tengas un problema de codificación, intenta llamarlo La_Coruna.svg a ver si le gusta. De todas formas, en general, intenta evitar los nombres de archivos con caracteres “raros” porque, aunque en tu máquina local funcione, tal vez al subirlo a un servidor puedas tener problemas, o incluso si haces migración entre servidores.

      Saludos!

      1. luis /
        Usando Google Chrome Google Chrome 48.0.2564.116 en Windows Windows 8

        Hola Gaspar gracias por la respuesta, pues ya lo llamé La_Coruna.svg pero sigue si funcionar. Tambien lo levante con apache tomcat, pero igual sigue sin funcionar, no se si hay alguna linea de codigo que tenga que modificar?

  5. alejandro /
    Usando Google Chrome Google Chrome 49.0.2623.87 en Windows Windows NT

    hola quisiera si tu me puedes hacer un mapa de chile con esas caracteristicas y me dices cuanto me cobras. gracias

    1. Gaspar Fernández / Post Author
      Usando Mozilla Firefox Mozilla Firefox 45.0 en Ubuntu Linux Ubuntu Linux

      Si alguien que entre por aquí se anima os pongo en contacto. Yo por el momento no tengo mucho tiempo. Saludos!

    2. Anibal Contreras /
      Usando Google Chrome Google Chrome 51.0.2704.103 en Mac OS X Mac OS X 10.11.6

      Hola yo lo logré hacer con el mapa de chile dividido en regiones, por si te interesa aún escríbeme xxxxxxxxxxxxx

      1. Gaspar Fernández / Post Author
        Usando Mozilla Firefox Mozilla Firefox 48.0 en Ubuntu Linux Ubuntu Linux

        Hola Anibal !! Ya te mandé un mail (he editado tu comentario para que nadie lo vea)

  6. Dann /
    Usando Google Chrome Google Chrome 51.0.2704.103 en Windows Windows 8

    Hola, quiero saber si esta libreria aun existe, esque reviso los links de para Raphaeljs y estan caidos.

    Probe el ejemplo mas sencillo que se pones en el articulo y no funciona, naturalmente accedi a la libreria por la URL: http://github.com/DmitryBaranovskiy/raphael/raw/master/raphael-min.js
    porque no tengo el js y no funciona.

    1. Gaspar Fernández / Post Author
      Usando Mozilla Firefox Mozilla Firefox 47.0 en Ubuntu Linux Ubuntu Linux

      Hola Dann,

      En Raphael han cambiado cosas, yo creo que el dueño ya no quería gastar dinero en el dominio, porque la biblioteca no reportaría dinero. Cosas que pasan, de todas formas, la dirección que das de GitHub, creo que es la nueva dirección para encontrarla. Voy a cambiarlo en el post. ¡Muchas gracias!

      Yo he logrado cargar los ejemplos con la nueva versión de la biblioteca sin problema. Si quieres, carga las herramientas para desarrolladores de tu navegador a ver si en la consola (o en Red) te devuelve algún mensaje que nos ayude a depurar.

  7. zeuss /
    Usando Google Chrome Google Chrome 53.0.2785.143 en Windows Windows 7

    Hola disculpa crees que de igual forma se pueda usar con un mapa de un estadio?

    1. Gaspar Fernández / Post Author
      Usando Mozilla Firefox Mozilla Firefox 48.0 en Ubuntu Linux Ubuntu Linux

      Claro que sí, al final lo que estamos manejando son imágenes svg por lo que si tienes el mapa (o lo pasas tú) a este formato, no deberías tener ningún problema.

  8. Kevin Edmundo TRUJILLO LOBATO /
    Usando Google Chrome Google Chrome 54.0.2840.71 en Windows Windows NT

    Que tal podrías enviarme el código fuente, quiero aplicar tu algoritmo pero a unos planos espero tu pronta respuesa.

    1. Gaspar Fernández / Post Author
      Usando Mozilla Firefox Mozilla Firefox 49.0 en Ubuntu Linux Ubuntu Linux

      Hola Kevin. El código fuente está aquí en el post para que todo el mundo lo coja 🙂

  9. jhonatan /
    Usando Google Chrome Google Chrome 61.0.3163.100 en Windows Windows 7

    hola, buenas tengo un problema al momento de llamar al mapa, no se a que se debe, lo iba a implementar en un proyecto usando jsf

Leave a Reply