Showing posts with label elections. Show all posts
Showing posts with label elections. Show all posts

Monday, February 25, 2013

D3js Electoral map

After trying to draw an electoral map using Kartograph, this time I've tried with D3js.
This has some good things, such as being able to use topoJSON which reduces dramatically the size of the files or using all the visualization tools included in D3js. On the other hand, Kartogrph has some good styling aids that help a lot.

As usual, you can download all the source code
or take a look to the examples:
Simple Map -- source code
Select Order Map -- source code
Simple Tooltip Map -- source code
Pie Chart Tooltip Map -- source code

Simple map

Let's start with the basic choropleth map:
The complete code for the example can be found here.
The image will be an SVG, so web can add interactivity and style it easily. 
The scripts included will be d3js, of course, and topojson, since this is the format of the data (see the last point):

 
The JavaScript part, then is:
var width = 600,
    height = 600;

var projection = d3.geo.mercator()
    .center([2,41.5])
    .scale(50000)
    .translate([width / 2, height / 2]);

var path = d3.geo.path()
    .projection(projection);

var svg = d3.select("#map").append("svg")
    .attr("width", width)
    .attr("height", height);

d3.json("mun_out_topo.json", function(error, topo) {
  svg.selectAll()
      .data(topojson.object(topo, topo.objects.mun_out).geometries)
    .enter().append("path")
      .attr("class", function(d) {
           var maxVotes = 0;
           var party = null;
           if (d.properties["CiU"]>maxVotes){maxVotes = d.properties["CiU"]; party="CiU";}
           if (d.properties["PSC"]>maxVotes){maxVotes = d.properties["PSC"]; party="PSC";}
           if (d.properties["ERC"]>maxVotes){maxVotes = d.properties["ERC"]; party="ERC";}
           if (d.properties["ICV-EUiA"]>maxVotes){maxVotes = d.properties["ICV-EUiA"]; party="ICV-EUiA";}
           if (d.properties["CUP"]>maxVotes){maxVotes = d.properties["CUP"]; party="CUP";}
           if (d.properties["C's"]>maxVotes){maxVotes = d.properties["C's"]; party="Cs";}
           return "municipality " + party; 
       })
      .attr("d", path);
});

  1. See, at line 4, how the projection is set. I have chosen Mecator and centered it at the coordinates I know are more or less at the center of my bounding box. Then with a lot of patience, just trying, I have found that the scale 50000 is the one that fits better for the image size.
  2. At line 9, the path object is set, and then at line 12, the SVG object is assigned to the div with the id=map. I prefer to put it into a div rather than directly to the body tag, as most of the d3js examples do.
  3. At line 16, the topoJSON is loaded, and the other stuff is run only after this is done. Do not put this code outside the function or it won't work.
  4. At line 17, the map drawing starts:
    1. The topoJSON elements are assigned as the data. In this case, the name of the elements is topo.objects.mun_out, but to find it, the best is to look directly into the topoJSON file.
    2. With the enter() method, the following methods will be applied to every element. The first thing done is appending a path element to the svg.
    3. The class is set, so the map can have different colours depending on the winning party. To do it, the function looks into the element properties. Again, take a look into the topoJSON file to see how the information is stored. The string returned is municipality and the winner party. The css at the header of the file sets the colour for the background and stroke.
    4. Finally, the path of the element is set to the svg, actually drawing the shape.

Selecting the order

The map above shows only the party that won in every municipality. What about changing that, choosing the position the user wants to show? With d3js is quite easy to do it.

The complete code for the example can be found here.
The part changed from the first example is after loading the topoJSON file:

d3.json("mun_out_topo.json", function(error, topo) {
  svg.selectAll("municipality")
      .data(topojson.object(topo, topo.objects.mun_out).geometries)
    .enter().append("path")
      .attr("class", function(d) {return "municipality " + selectParty(d,1);})
      .attr("d", path);
       
      function selectParty(d,position){
   
           var positions = new Array();
           positions[0] = parseInt(d.properties["CiU"]);
           positions[1] = parseInt(d.properties["PSC"]);
           positions[2] = parseInt(d.properties["ERC"]);
           positions[3] = parseInt(d.properties["PP"]);
           positions[4] = parseInt(d.properties["ICV-EUiA"]);
           positions[5] = parseInt(d.properties["CUP"]);
           positions[6] = parseInt(d.properties["C's"]);
 
           positions.sort(function(a,b) { return b-a; });
            
           var party = null;
           if (positions[position-1] == parseInt(d.properties["CiU"])){
               party = "CiU";
           } else if (positions[position-1] == parseInt(d.properties["PSC"])){
               party = "PSC";              
           } else if (positions[position-1] == parseInt(d.properties["ERC"])){
               party = "ERC";              
           } else if (positions[position-1] == parseInt(d.properties["PP"])){
               party = "PP";              
           } else if (positions[position-1] == parseInt(d.properties["ICV-EUiA"])){
               party = "ICV-EUiA";              
           } else if (positions[position-1] == parseInt(d.properties["CUP"])){
               party = "CUP";              
           } else if (positions[position-1] == parseInt(d.properties["C's"])){
               party = "Cs";              
           }
     
           return party;
      }
       
      d3.select("#position").on("change", function() {
            
           var position = parseInt(this.value);
           svg.transition()
           .selectAll(".municipality")
           .attr("class", function(d) {return "municipality " + selectParty(d,position);});
      });
 
});

  1.  At line 8, note that a new function is defined. The function returns the class name depending on the order position, passed as a parameter. At line 5, the function is called for the first time, asking for the first position.
  2. At line 41, an event method is added. When the selector changes its value, a transition is passed to the svg, calling the selectParty method to re-calculate the classes.
As you can see, modifying the properties of all the svg objects is quite simple.

Adding tooltips

Showing the results for a selected municipality when the mouse is over is also quite simple and improves a lot the map.
The complete code of the example can be found here

The tooltip is created using the files from this example. (although styled to make it contrast a little, and commenting the line 20)

d3.json("mun_out_topo.json", function(error, topo) {
  svg.selectAll("municipality")
      .data(topojson.object(topo, topo.objects.mun_out).geometries)
    .enter().append("path")
      .attr("class", function(d) {return "municipality " + selectParty(d,1);})
      .attr("d", path)
      .call(d3.helper.tooltip(function(d, i){return tooltipText(d);}));
      
      function selectParty(d,position){
  
           var positions = new Array();
           positions[0] = parseInt(d.properties["CiU"]);
           positions[1] = parseInt(d.properties["PSC"]);
           positions[2] = parseInt(d.properties["ERC"]);
           positions[3] = parseInt(d.properties["PP"]);
           positions[4] = parseInt(d.properties["ICV-EUiA"]);
           positions[5] = parseInt(d.properties["CUP"]);
           positions[6] = parseInt(d.properties["C's"]);

           positions.sort(function(a,b) { return b-a; });
           
           var party = null;
           if (positions[position-1] == parseInt(d.properties["CiU"])){
               party = "CiU";
           } else if (positions[position-1] == parseInt(d.properties["PSC"])){
               party = "PSC";               
           } else if (positions[position-1] == parseInt(d.properties["ERC"])){
               party = "ERC";               
           } else if (positions[position-1] == parseInt(d.properties["PP"])){
               party = "PP";               
           } else if (positions[position-1] == parseInt(d.properties["ICV-EUiA"])){
               party = "ICV-EUiA";               
           } else if (positions[position-1] == parseInt(d.properties["CUP"])){
               party = "CUP";               
           } else if (positions[position-1] == parseInt(d.properties["C's"])){
               party = "Cs";               
           }
    
           return party;
      }
      
      function tooltipText(d){
           return "" + d.properties["Name"] + ""
                  + "
 CiU: " + d.properties["CiU"] 
                  + "
 PSC: " + d.properties["PSC"]
                  + "
 ERC: " + d.properties["ERC"]
                  + "
 PP: " + d.properties["PP"]
                  + "
 ICV-EUiA: " + d.properties["ICV-EUiA"]
                  + "
 CUP: " + d.properties["CUP"]
                  + "
 C's: " + d.properties["C's"];
      }
      d3.select("#position").on("change", function() {
           
           var position = parseInt(this.value);
           svg.transition()
           .selectAll(".municipality")
           .attr("class", function(d) {return "municipality " + selectParty(d,position);});
      });

});
Again, the code needs only small changes:
  1. At line 7, the event is added to each feature. I have separated the text generation into a function to make it easier to understand.
  2. At line  42 the function tooltipText is defined. It just returns the desired text getting all the properties from each feature.

Cool tooltips using d3js

The best thing about using d3js is that you can mix all its visual possibilities, which are infinite. In the electoral map case, a donut chart helps a lot when interpreting the numbers, at least, much more than showing only the number of votes, that cchange a lot in every municipality.
The complete code for the example can be found  here

I have taken the donut chart code from this example, and the label positions from this other example.

d3.helper = {};
d3.helper.tooltip = function (accessor){
    return function(selection){
 var tooltipDiv;
        var bodyNode = d3.select('body').node();
        selection.on("mouseover", function(d, i){
            d3.select('body').selectAll('div.tooltip').remove();
            tooltipDiv = d3.select('body').append('div').attr('class', 'tooltip');
            var absoluteMousePos = d3.mouse(bodyNode);
            tooltipDiv.style('left', (absoluteMousePos[0] + 10)+'px')
                .style('top', (absoluteMousePos[1] - 15)+'px')
                .style('position', 'absolute') 
                .style('z-index', 1001);
            var arc = d3.svg.arc()
                .outerRadius(120)
                .innerRadius(40);

            var pie = d3.layout.pie()
               .sort(null)
               .value(function(d) { return d.votes; });

            var svg = tooltipDiv.append("svg")
                .attr("width", 270)
                .attr("height", 300)
                .append("g")
                .attr("transform", "translate(" + 270 / 2 + "," + 270 / 2 + ")");
 
            var data = [
                {'party':"CiU",'votes':d.properties["CiU"]},
                {'party':"PSC",'votes':d.properties["PSC"]},
                {'party':"ERC",'votes':d.properties["ERC"]},
                {'party':"PP",'votes':d.properties["PP"]},
                {'party':"ICV",'votes':d.properties["ICV-EUiA"]},
                {'party':"CUP",'votes':d.properties["CUP"]}, 
                {'party':"C's",'votes':d.properties["C's"]}
            ];
            data.forEach(function(d) {
                d.votes = +d.votes;
            });

  

            var g = svg.selectAll(".arc")
               .data(pie(data))
               .enter().append("g")
               .attr("class", "arc");

            g.append("path")
              .attr("d", arc)
              .style("fill", function(d) { return color(d.data.party); });
            g.append("text")
              .attr("transform", function(d) { var angle =(180/Math.PI) * (d.startAngle + (d.endAngle-d.startAngle)/2); return "translate(" + arc.centroid(d) + ") rotate("+angle+", 0,0)"; })
              .attr("dy", "-2.5em")
              .style("text-anchor", "middle")
              .text(function(d) { return d.data.party; });

  

          var municipality = d.properties['Name'];
          
          svg.append("text")
              .attr("transform", "translate(0,140)")
              .attr("dy", ".35em")
              .style("text-anchor", "middle")
              .text(municipality);
            
                      
        })
        .on('mousemove', function(d, i) {
            var absoluteMousePos = d3.mouse(bodyNode);
            tooltipDiv.style('left', (absoluteMousePos[0] + 10)+'px')
                .style('top', (absoluteMousePos[1] - 15)+'px');
            var tooltipText = accessor(d, i) || '';
            //tooltipDiv.html(tooltipText);
            
        })
        .on("mouseout", function(d, i){
            tooltipDiv.remove();
        });
    };    
};
This piece of code is put before loading the topoJSON. Is more or less this example, adapted to show the parties results (line 28) and puting the labels using an angle (line 52). Notice that first, the rotation is done, and only then the translation. At line 53, the label is moved outside the pie.

The tooltip is added as in the previous example.

The data

Preparing the data has been, again, a problem. Since the Government gives the maps with a code (INE code) and the electoral results with another (alphabetical order), I've had to manipulate the files to merge them, by comparing the municipalities names. Besides, some of the names contain different abbreviations in each file, so they have to be changed by hand...

The files used are:
  • The election results. Is a CSV file with all the municipalities, plus some regions and Barcelona quarters. I have cleaned them so only the municipalities are present. Besides, the file is encoded in Latin1, and the shapefile in UTF-8, so I have converted it using:
    iconv -f latin1 -t utf-8 OPENDATA_A2012_vots.csv > newfile
  • The municipalities shapefile. I have get it from the Vissir3 web site
  • To merge both, I have made a small python script, uploaded to GitHub if you are interested in the code. 
To convert it to TopoJSON, I have run first:

ogr2ogr -simplify 0.001 -f GeoJSON municipis.json  municipis.shp

Simplifying the data so the file is smaller (the number is guessed just by trying many times to get the best size/quality relation)

and later:

topojson -p Name=Name -p ERC=ERC -p CiU=CiU -p PP=PP -p PSC=PSC -p ICV-EUiA=ICV-EUiA -p CUP=CUP -p "C's"="Cs"  -o mun_out_topo.json mun_out.json


To convert JSON to TopoJSON.


Saturday, November 24, 2012

Kartograph tutorial: Electoral map

Tomorrow I will be all the day at the polling station, since I've been chosen as a member.
The last two weeks I was playing with the amazing Kartograph software, so it was a good moment to experiment with electoral maps (the first time for me).

 In this example, I will explain step by step how to create the map above. It's quite similar to this tutorial, but I want to continue in a new post, going interactive.
Kartograph creates vector SVG images, which you can edit later with Inkscape or Illustrator, so gives much more flexibility than systems generating PNG like files, much more difficult to modify.
As in all the posts, you can get all the files used in the example.
This example has two continuation posts: 

Getting the data

As usual, getting the data is not so easy. The Catalan government has a good open data web site, where I found:
  • A file with a really descriptive name, bm50mv33sh1fc1r170.zip, with all the administrative boundaries (provinces, comarques, and municipalities).
  • Lots of files with election results. I choose the 2010 elections, since they where to the Catalan parliament, like the ones tomorrow. As you can see on the map, the party CiU won with a very big majority, so the map is not as interesting as it could be.
I have used the municipalities to draw the map because the result is more diverse than using bigger zones. Actually, the real  constituency is the province, but CiU won everywhere, and a plain blue map is quite boring.
So I've had to join the two files to get one file with the geometries and the results. The process is quite long and dirty (why didn't they use an id? I had to join with the names), so I won't explain how to do it, but put the result at the data files. You can find this file here.

Then, to decorate the map, I used the following files
  • World boundaries from Natural Earth (ne_10m_admin_0_countries.zip), to draw the coast line outside Catalonia
  • From VMAP, the layers Trees, Crops, and DepthContours, to decorate the map outside the electoral  constituencies.
Since the layers are worldwide, so very big, I have used these ogr commands to clip:
 ogr2ogr -clipsrc -3 37 4 44 Trees2.shp Trees.shp
and to simplify:
 ogr2ogr -simplify 10 munis.shp bm50mv33sh1fpm1r170.shp
Doing so, the time to generate the map is divided by five or more.

Installing Kartograph

Since we only need kartograph.py for this tutorial,  first, download it from the github page clicking at the zip icon.
In a linux system, uncompress and execute 
python setup install
as a super user.
That's all, if you have the GDAL python bindings installed.

Creating the map

To create a map with Kartograph, you will need a configuration file in JSON format, which will have three basic sections:

Projection

To set the projection, there used to be a web page named Visual map configurator, that doesn't work any more. But don't worry, you can use the Map Projections page. Just choose the projection that fits you more, change the parameters and click the gear icon:
A dialog will open, and the lines that are interesting in this case are, in the image example, like:
         "proj": "sinusoidal",
        "lon0": 20
This will be translated in our json file as:
     "proj": {
            "id": "sinusoidal",
           "lon0": 20
     }

 Bounds:

The part of the world we want to represent is set here. It's quite well explained at the documentation, but it can be a bit confusing, and not all the options work with all the projections.
In our example, I have used:
  "bounds": {
    "mode": "bbox",
    "data": [-0, 40, 4, 43],
    "crop": [-3, 37, 5, 44]
  } 

  • mode: How the bounds are expressed. BBOX is the basic option, but you can also set it defining the points you want to enter in the map, or even the features in a layer. If the layers are in different projections, other modes can be a little tricky.
  • data: In our case, the bounding box. In other modes, the layer name, the points, or whatever.
  • crop: Is an optional label. Our svg will be clipped at the bounds set at data, but all the data in the files will be processed. If the files include all the world, this takes a long time, and generates much bigger SVG outputs. With crop, only the features inside the BBOX will be included.

Layers:

As the name suggests, the layers to include. 
The shapefiles are added as:
   "municipalities":{
       "src": "./mun_out.shp"
   }
There are also two special layer, graticule and sea. The first draws the meridians ans parallels, while the second does nothing more than giving a feature to draw the background:
   "background": {"special": "sea"},
   "graticule":{ "special": "graticule", "latitudes": 1, "longitudes": 1}

All the layers  will be drawn in the order indicated at the json file, so this must be well chosen to select which layer hides what.

Styling

This is the nice part. Without styling, the SVG can be used directly with Inkscape or Kartograph.js, but is possible to generate styled maps directly with kartograph.py.
You can give the style either in the json file or in a separate css file, which seems cleaner. The names given to the layer are the ones to be used in the css as the id. So to give a style to the municipalities layer, add
#municipalities {
 fill: #FFF;
 stroke: #882222;
 stroke-width: 0.5px;
 stroke-opacity: 0.4;
}
The general options are at the documentation again. CSS for SVG is a little different from the one used in traditional html.
Since we want to paint the municipalities in a different color depending of the party who won the elections, we will use filters, like this one:
#municipalities[Winner=CiU]{
 fill: #99edff;
}

It would be nice to compare different fields i.e. CiU > PSOE, but this is not possible (at least, I haven't found how to do it), so I had to calculate the winner and put it in a field (called Winner, as you can see in the example)

Drawing

There are two options to draw the map. A command line program is installed with the setup, called kartograph. 
To draw the styled map, just type
   kartograph elections.json --style elections.css -o elections.svg
But you can also include all this in a python program, so could generate the data and then the map. In our case, the code would be
from kartograph import Kartograph
from kartograph.options import read_map_descriptor
import sys
K = Kartograph()
css = open("elections.css").read()
cfg = read_map_descriptor(open("elections.json"))
K.generate(cfg, outfile='elections.svg', format='svg', stylesheet=css) 
 
Finally, I edited the svg file with Inkscape to put the titles and legend. Is just to show that the idea is generating a base svg and from there, draw the pretty final map.

Configuration files

To draw the map in the example, I have used the following files:
elections.json
{
"proj": {
        "id": "sinusoidal",
        "lon0": 20
  },
   "layers": {
   "background": {"special": "sea"},
   "graticule":{ "special": "graticule", "latitudes": 1, "longitudes": 1, "styles": { "stroke-width": "0.3px" } },
    "world":{
       "src": "data/ne_10m_admin_0_countries2.shp"
   }, 
   "trees":{
      "src": "data/Trees2.shp",
      "simplify": true
   },
   "crops":{
      "src": "data/Crops2.shp",
      "simplify": true
   },
   "depth": {
       "src": "data/DepthContours2.shp",
       "simplify": true
   },
   "municipalities":{
       "src": "./mun_out.shp"
   }
   },
  "bounds": {
    "mode": "bbox",
    "data": [-0, 40, 4, 43],
    "crop": [-3, 37, 5, 44]
  }
}

elections.css
#background {
 fill: #e8f9fb;
 stroke: none;
},
#world {
 fill: #f5f3f2;
 stroke: none;
},
#graticule {
 stroke-width: 0.3px;
},
#municipalities {
 fill: #FFF;
 stroke: #882222;
 stroke-width: 0.5px;
 stroke-opacity: 0.4;
},
#municipalities-label {
 font-family: Arial;
 font-size: 13px;
},
#municipalities[Winner=CiU]{
 fill: #99edff;
},
#municipalities[Winner=PSC-PSOE]{
 fill: #ff9999;
},
#municipalities[Winner=ERC]{
 fill: #EDE61A;
},
#depth {
 stroke: #223366;
 stroke-width: 0.5px;
 stroke-opacity: 0.4;
},
#trees {
  fill: #d2f8c0;
  stroke: none;
},
#crops {
  fill: #fcf8d8;
  stroke: none;
}

What's next

If I have time, I'll try my first Kartograph.js example. From the svg generated, is possible to create cool interactive maps.