block by michalskop ab38ec6b9b86de17bce9

CZ: Praha 2010-2014 (WPCA, Python+R, reusable charts)

Full Screen

index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>Scatterplot Chart</title>
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta name="description" content="scatter plot">
    <meta name="author" content="Michal Škop">
    <script src="//d3js.org/d3.v3.min.js"></script>
    <script src="./d3.scatterplotwithlineplot.js"></script>
    <script src="./d3.tips.js"></script>
    <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootswatch/3.2.0/simplex/bootstrap.min.css" />
    <style type="text/css">
			
			text {
			  font-family: sans-serif;
			}
			
			.tick {
			  fill-opacity: 0;
			  stroke: #000000;
			  stroke-width: 1;
			}
			
			.domain {
			    fill: none;
				fill-opacity: 0;
				stroke: black;
				stroke-width: 1;
			}
			.axis line {
				fill: none;
				fill-opacity: 0;
				stroke: black;
				stroke-width: 1;
				shape-rendering: crispEdges;
			}
			
			.axis text {
				font-family: sans-serif;
				font-size: 11px;
				stroke: gray;
			}
			circle {
			  fill-opacity: .3;
			  stroke: black;
			  stroke-opacity: 0.99;
			}
			circle:hover {
			  fill-opacity: 1;
			}
			.label {
			  font-family: sans-serif;
			  font-size: 15px;
			}
			.cssd {
			  fill: orange;
			  stroke: orange;
			}
			.kscm {
			  fill: red;
			  stroke: red;
			}
			.ods {
			  fill: blue;
			  stroke: blue;
			}
			.top-09 {
			  fill: #b0b;
			  stroke: #b0b;
			}
			.d3-tip {
                line-height: 1;
                font-weight: bold;
                padding: 12px;
                background: rgba(0, 0, 0, 0.8);
                color: #fff;
                border-radius: 2px;
                pointer-events: none;
                max-width: 250px;
            }
            /* Creates a small triangle extender for the tooltip */
            .d3-tip:after {
                box-sizing: border-box;
                display: inline;
                font-size: 10px;
                width: 100%;
                line-height: 1;
                color: rgba(0, 0, 0, 0.8);
                position: absolute;
                pointer-events: none;
            }
            /* Northward tooltips */
            .d3-tip:after {
                content: "\25BC";
                margin: -1px 0 0 0;
                top: 100%;
                left: 0;
                text-align: center;
            }
            line {
             stroke:gray;
             stroke-width:1;
             opacity: .15;
            }
            .perfect {
              stroke: gray;
              stroke-width:3;
              opacity: 0.5;
            }
            .part-1 {
              stroke: red;
              stroke-width:1;
              opacity: 0.15;
            }
            .part-2 {
              stroke: green;
              stroke-width:1;
              opacity: 0.15;
            }
            .part-3 {
              stroke: blue;
              stroke-width:1;
              opacity: 0.15;
            }
            

		</style>
  </head>
  <body>
    <div class="navbar navbar-default">
      <div class="navbar-header">
       <h1 class="navbar-brand">Prague 2010 - 2014</h1>
      </div>
    </div>
    <div id="chart"></div>
    <div class="alert alert-info">
      WPCA
    </div>
    
<script type="text/javascript">
d3.csv("praha-2010-2014-roll-call-votes-wpca-cutting-lines.csv",function(error,lines) {
  d3.csv("praha-2010-2014-roll-call-votes-wpca.csv",function(error,csvdata) {
    linesselected = []
    for (k in lines) {
      if ((parseFloat(lines[k]['w2']) > 0.5) && (parseFloat(lines[k]['cl_beta0']) < 50)) {
      //if (1>0){
          beta = [lines[k]['normal_x'],lines[k]['normal_y']];
          beta0 = lines[k]['cl_beta0'];
          if (beta[1] != 0) {
            lines[k]['a'] = -beta0/beta[1];
            lines[k]['b'] = -beta[0]/beta[1];
          } else {
            lines[k]['a'] = 0;
            lines[k]['b'] = 0;
          }
          //colored by perfect cut:
          /*if (lines[k]['loss'] == 0) {
            lines[k]['class'] = 'perfect';
          } else {
            lines[k]['class'] = 'non-perfect';
          }*/
          // colored by time
          if (k < 690) {
            lines[k]['class'] = 'part-1';
          } else {
            if (k < 1815) {
              lines[k]['class'] = 'part-2';
            } else {
              lines[k]['class'] = 'part-3';
            }
          }
          lines[k]['name'] = "i:"+k+"<br>w1:"+lines[k]['w1']+"<br>w2:"+lines[k]['w2']+"<br>loss:"+lines[k]['loss']+"<br>name:"+lines[k]['motion:name'];
          linesselected.push(lines[k]);
      }
    }

    spdata = [];
    csvdata.forEach(function(d) {
      spdata.push({"x":d["wpca:d1"],"y":d["wpca:d2"],"r":1,"class":d["class"],"name":d["name"]})
    });
    var scatterplotwithlineplot = [{
      "data": spdata,
      "margin": {top: 10, right: 10, bottom: 30, left: 30},
      "axes": {"labels":{"x":"Dimension 1", "y":"Dimension 2"}},
      "minmax":{"x":{'min':-18,'max':18},"y":{'min':-18,'max':18},"r":{'min':0,'max':1},"rrange":{'min':0,'max':10}},
      "size":{"width":600,"height":400},
      "lines": linesselected
    }];
    
     var svg = d3.select("#chart")
        .append("svg")
        .attr("width",scatterplotwithlineplot[0]['size']['width'])
        .attr("height",scatterplotwithlineplot[0]['size']['height']);
        
    /* Initialize tooltip */
    tip = d3.tip().attr('class', 'd3-tip').html(function(d) { 
      return "<span class=\'stronger\'>" + d["name"] + "</span><br>";
    });

    /* Invoke the tip in the context of your visualization */
    svg.call(tip)
    
    var sp = d3.scatterplotwithlineplot()
        .data(function(d) {return d.data})
        .margin(function(d) {return d.margin})
        .axes(function(d) {return d.axes})
        .minmax(function(d) {return d.minmax})
        .size(function(d) {return d.size})
        .lines(function(d) {return d.lines})
    
    var scatter = svg.selectAll(".scatterplot")
        .data(scatterplotwithlineplot)
      .enter()
        .append("svg:g")
        .attr("transform", "translate(" + scatterplotwithlineplot[0].margin.left + "," + scatterplotwithlineplot[0].margin.top + ")")
        .call(sp);
    
  });
});
</script>
  </body>
</html>

d3.scatterplot.js

/* requires D3 + https://github.com/Caged/d3-tip */
d3.scatterplot = function() {
  function scatterplot(selection) {
    selection.each(function(d, i) {
      //options
      var data = (typeof(data) === "function" ? data(d) : d.data),
          margin = (typeof(margin) === "function" ? margin(d) : d.margin),
          axes = (typeof(axes) === "function" ? axes(d) : d.axes),
          minmax = (typeof(minmax) === "function" ? minmax(d) : d.minmax),
          size = (typeof(size) === "function" ? size(d) : d.size);
      
      // chart sizes
      var width = size['width'] - margin.left - margin.right,
          height = size['height'] - margin.top - margin.bottom;
      
      //scales
      var xScale = d3.scale.linear()
							 .domain([minmax['x']['min'], minmax['x']['max']])
							 .range([0, width])

      var yScale = d3.scale.linear()
							 .domain([minmax['y']['min'], minmax['y']['max']])
							 .range([height, 0])

      var rScale = d3.scale.linear()
							 .domain([minmax['r']['min'],minmax['r']['max']])
							 .range([minmax['rrange']['min'],minmax['rrange']['max']]);

      //axes
      var xAxis = d3.svg.axis()
        .scale(xScale)
        .orient("bottom");
        //.ticks(5);
        //.tickSize(16, 0);  
      var yAxis = d3.svg.axis()
        .scale(yScale)
        .orient("left");
        //.ticks(5); 
      

      var element = d3.select(this);
      
		//Create X axis
      element.append("g")
			.attr("class", "axis x-axis")
			.attr("transform", "translate(0," + height + ")")
			.call(xAxis);
		
		//Create Y axis
      element.append("g")
			.attr("class", "axis y-axis")
			.call(yAxis);
      
      element.selectAll("circle")
        .data(data)
		   .enter()
		 .append("circle")
		   .attr("cx", function(d) {
		   		return xScale(d.x);
		   })
		   .attr("cy", function(d) {
		   		return yScale(d.y);
		   })
		   .attr("r", function(d) {
		   		return rScale(1);
		   })
		   .attr("class", function(d) {
		   		if (typeof(d['class'] != 'undefined')) return d['class'];
		   		else return 'circle';
		   })
		   .on('mouseover', tip.show)
           .on('mouseout', tip.hide);
	
      //axis labels
	  element.append("text")
			.attr("class", "x-label label")
			.attr("text-anchor", "end")
			.attr("x", width)
			.attr("y", height-5)
			.text(axes['labels']['x']);
	  element.append("text")
			.attr("class", "y label")
			.attr("text-anchor", "end")
			.attr("y", 5)
			.attr("x", 0)
			.attr("dy", ".75em")
			.attr("transform", "rotate(-90)")
			.text(axes['labels']['y']);
			 
		
    });
  }
  scatterplot.data = function(value) {
    if (!arguments.length) return value;
    data = value;
    return scatterplot;
  };  
  scatterplot.margin = function(value) {
    if (!arguments.length) return value;
    margin = value;
    return scatterplot;
  };
  scatterplot.axes = function(value) {
    if (!arguments.length) return value;
    axes = value;
    return scatterplot;
  };
  scatterplot.minmax = function(value) {
    if (!arguments.length) return value;
    minmax = value;
    return scatterplot;
  };
  scatterplot.size = function(value) {
    if (!arguments.length) return value;
    size = value;
    return scatterplot;
  };
  
  return scatterplot;
}

d3.scatterplotwithlineplot.js

/* requires D3 + https://github.com/Caged/d3-tip */
d3.scatterplotwithlineplot = function() {
  function scatterplotwithlineplot(selection) {
    selection.each(function(d, i) {
      //options
      var data = (typeof(data) === "function" ? data(d) : d.data),
          lines = (typeof(lines) === "function" ? lines(d) : d.lines),
          margin = (typeof(margin) === "function" ? margin(d) : d.margin),
          axes = (typeof(axes) === "function" ? axes(d) : d.axes),
          minmax = (typeof(minmax) === "function" ? minmax(d) : d.minmax),
          size = (typeof(size) === "function" ? size(d) : d.size);
      
      // chart sizes
      var width = size['width'] - margin.left - margin.right,
          height = size['height'] - margin.top - margin.bottom;
      
      //scales
      var xScale = d3.scale.linear()
							 .domain([minmax['x']['min'], minmax['x']['max']])
							 .range([0, width])

      var yScale = d3.scale.linear()
							 .domain([minmax['y']['min'], minmax['y']['max']])
							 .range([height, 0])

      var rScale = d3.scale.linear()
							 .domain([minmax['r']['min'],minmax['r']['max']])
							 .range([minmax['rrange']['min'],minmax['rrange']['max']]);

      //axes
      var xAxis = d3.svg.axis()
        .scale(xScale)
        .orient("bottom");
        //.ticks(5);
        //.tickSize(16, 0);  
      var yAxis = d3.svg.axis()
        .scale(yScale)
        .orient("left");
        //.ticks(5); 
      

      var element = d3.select(this);
      
		//Create X axis
      element.append("g")
			.attr("class", "axis x-axis")
			.attr("transform", "translate(0," + height + ")")
			.call(xAxis);
		
		//Create Y axis
      element.append("g")
			.attr("class", "axis y-axis")
			.call(yAxis);
      
      limits = {"x":[minmax["x"]["min"],minmax["x"]["max"]],"y":[minmax["y"]["min"],minmax["y"]["max"]]}
      for (k in lines) {
        ps = linecross(lines[k],limits);
        if (ps.length == 2) {
          lines[k].x1 = ps[0][0];
          lines[k].y1 = ps[0][1];
          lines[k].x2 = ps[1][0];
          lines[k].y2 = ps[1][1];
        }
        way = get_sign(lines[k].b,lines[k].n1,lines[k].n2);
        lines[k].path =[corners(lines[k],limits,way),corners(lines[k],limits,-1*way)];
      }
      
      var line = element.selectAll ('.line')
         .data(lines)
         .enter()
            .append("line")
         .attr("x1",function(d) {return xScale(d.x1)})
         .attr("y1",function(d) {return yScale(d.y1)})
         .attr("x2",function(d) {return xScale(d.x2)})
         .attr("y2",function(d) {return yScale(d.y2)})
         .attr("id", function (d, i) {return "q-" + i;})
         .attr("class", function(d) {
		   		if (typeof(d['class'] != 'undefined')) return d['class'];
		   		else return 'line';
		   })
         .on('mouseover', tip.show)
         .on('mouseout', tip.hide);
      
      element.selectAll(".circle")
        .data(data)
		   .enter()
		 .append("circle")
		   .attr("cx", function(d) {
		   		return xScale(d.x);
		   })
		   .attr("cy", function(d) {
		   		return yScale(d.y);
		   })
		   .attr("r", function(d) {
		   		return rScale(1);
		   })
		   .attr("class", function(d) {
		   		if (typeof(d['class'] != 'undefined')) return d['class'];
		   		else return 'circle';
		   })
		   .on('mouseover', tip.show)
           .on('mouseout', tip.hide);
	
      //axis labels
	  element.append("text")
			.attr("class", "x-label label")
			.attr("text-anchor", "end")
			.attr("x", width)
			.attr("y", height-5)
			.text(axes['labels']['x']);
	  element.append("text")
			.attr("class", "y label")
			.attr("text-anchor", "end")
			.attr("y", 5)
			.attr("x", 0)
			.attr("dy", ".75em")
			.attr("transform", "rotate(-90)")
			.text(axes['labels']['y']);
			 
		
    });
  }
  scatterplotwithlineplot.data = function(value) {
    if (!arguments.length) return value;
    data = value;
    return scatterplotwithlineplot;
  };
  scatterplotwithlineplot.lines = function(value) {
    if (!arguments.length) return value;
    lines = value;
    return scatterplotwithlineplot;
  };    
  scatterplotwithlineplot.margin = function(value) {
    if (!arguments.length) return value;
    margin = value;
    return scatterplotwithlineplot;
  };
  scatterplotwithlineplot.axes = function(value) {
    if (!arguments.length) return value;
    axes = value;
    return scatterplotwithlineplot;
  };
  scatterplotwithlineplot.minmax = function(value) {
    if (!arguments.length) return value;
    minmax = value;
    return scatterplotwithlineplot;
  };
  scatterplotwithlineplot.size = function(value) {
    if (!arguments.length) return value;
    size = value;
    return scatterplotwithlineplot;
  };
  
  return scatterplotwithlineplot;
  
  
    function corners(l,e,o) {
      //l = {"a":0,"b":1}  //line, a and slope, i.e., y=a+bx
      //e = {"x": [-10,10], "y": [-10,10]} //limits
      //o = 1 //orientation -1 or 1

      //crossing x0, x1  
      //crossing y0, y1
      outp = linecross (l,e);
      
      out = [];

      //vertices
      for (i=0;i<=1;i++){
        for (j=0;j<=1;j++){
          if (o*(l.a+l.b*e.x[i]-e.y[j]) > 0)
            outp.push([e.x[i],e.y[j]]);
        }
      }
      //sort the outps, anticlockwise
      if (outp.length > 0) {
        mid = [0,0];
        for (i in outp) {
          mid[0] += outp[i][0];
          mid[1] += outp[i][1];
        }
        mid[0] = mid[0] / outp.length;
        mid[1] = mid[1] / outp.length;
        for (i in outp) {
          p = outp[i][1] - mid[1];
          q = outp[i][0] - mid[0];
          if (q != 0)
            outp[i][2] = Math.atan(p/q) + (q<0 ? Math.PI : 0);
          else
            outp[i][2] = Math.PI/2 + Math.PI*sign(p);
        }
        outp = outp.sort(function(w,z) {
          return w[2] > z[2];
        });
        for (i in outp) {
          outp[i].splice(2,1);
          out.push({"x":outp[i][0],"y":outp[i][1]});
        }
      }
      return out;
    }
  
  function linecross (l,e) {
      out = [];
      //crossing x0, x1
      for (i=0;i<=1;i++){
        Y = l.a + l.b*e.x[i];
        if ((Y > e.y[0]) && (Y < e.y[1]))
          out.push([e.x[i],Y]);
      }
      //crossing y0, y1
      for (j=0;j<=1;j++){
        if (l.b != 0) {
          X = (e.y[j] - l.a)/l.b;
          if ((X > e.x[0]) && (X < e.x[1]))
            out.push([X,e.y[j]]);
        }
      }
      return out;
    }

    function get_sign(b,d1,d2) {
      t = b*d1-d2;
      if (t > 0) return 1;
      if (t < 0) return -1;
      return 0;
    }
  
}

d3.tips.js

// d3.tip
// Copyright (c) 2013 Justin Palmer
//
// Tooltips for d3.js SVG visualizations

(function (root, factory) {
  if (typeof define === 'function' && define.amd) {
    // AMD. Register as an anonymous module with d3 as a dependency.
    define(['d3'], factory)
  } else if (typeof module === 'object' && module.exports) {
    // CommonJS
    module.exports = function(d3) {
      d3.tip = factory(d3)
      return d3.tip
    }
  } else {
    // Browser global.
    root.d3.tip = factory(root.d3)
  }
}(this, function (d3) {

  // Public - contructs a new tooltip
  //
  // Returns a tip
  return function() {
    var direction = d3_tip_direction,
        offset    = d3_tip_offset,
        html      = d3_tip_html,
        node      = initNode(),
        svg       = null,
        point     = null,
        target    = null

    function tip(vis) {
      svg = getSVGNode(vis)
      point = svg.createSVGPoint()
      document.body.appendChild(node)
    }

    // Public - show the tooltip on the screen
    //
    // Returns a tip
    tip.show = function() {
      var args = Array.prototype.slice.call(arguments)
      if(args[args.length - 1] instanceof SVGElement) target = args.pop()

      var content = html.apply(this, args),
          poffset = offset.apply(this, args),
          dir     = direction.apply(this, args),
          nodel   = d3.select(node),
          i       = directions.length,
          coords,
          scrollTop  = document.documentElement.scrollTop || document.body.scrollTop,
          scrollLeft = document.documentElement.scrollLeft || document.body.scrollLeft

      nodel.html(content)
        .style({ opacity: 1, 'pointer-events': 'all' })

      while(i--) nodel.classed(directions[i], false)
      coords = direction_callbacks.get(dir).apply(this)
      nodel.classed(dir, true).style({
        top: (coords.top +  poffset[0]) + scrollTop + 'px',
        left: (coords.left + poffset[1]) + scrollLeft + 'px'
      })

      return tip
    }

    // Public - hide the tooltip
    //
    // Returns a tip
    tip.hide = function() {
      var nodel = d3.select(node)
      nodel.style({ opacity: 0, 'pointer-events': 'none' })
      return tip
    }

    // Public: Proxy attr calls to the d3 tip container.  Sets or gets attribute value.
    //
    // n - name of the attribute
    // v - value of the attribute
    //
    // Returns tip or attribute value
    tip.attr = function(n, v) {
      if (arguments.length < 2 && typeof n === 'string') {
        return d3.select(node).attr(n)
      } else {
        var args =  Array.prototype.slice.call(arguments)
        d3.selection.prototype.attr.apply(d3.select(node), args)
      }

      return tip
    }

    // Public: Proxy style calls to the d3 tip container.  Sets or gets a style value.
    //
    // n - name of the property
    // v - value of the property
    //
    // Returns tip or style property value
    tip.style = function(n, v) {
      if (arguments.length < 2 && typeof n === 'string') {
        return d3.select(node).style(n)
      } else {
        var args =  Array.prototype.slice.call(arguments)
        d3.selection.prototype.style.apply(d3.select(node), args)
      }

      return tip
    }

    // Public: Set or get the direction of the tooltip
    //
    // v - One of n(north), s(south), e(east), or w(west), nw(northwest),
    //     sw(southwest), ne(northeast) or se(southeast)
    //
    // Returns tip or direction
    tip.direction = function(v) {
      if (!arguments.length) return direction
      direction = v == null ? v : d3.functor(v)

      return tip
    }

    // Public: Sets or gets the offset of the tip
    //
    // v - Array of [x, y] offset
    //
    // Returns offset or
    tip.offset = function(v) {
      if (!arguments.length) return offset
      offset = v == null ? v : d3.functor(v)

      return tip
    }

    // Public: sets or gets the html value of the tooltip
    //
    // v - String value of the tip
    //
    // Returns html value or tip
    tip.html = function(v) {
      if (!arguments.length) return html
      html = v == null ? v : d3.functor(v)

      return tip
    }

    function d3_tip_direction() { return 'n' }
    function d3_tip_offset() { return [0, 0] }
    function d3_tip_html() { return ' ' }

    var direction_callbacks = d3.map({
      n:  direction_n,
      s:  direction_s,
      e:  direction_e,
      w:  direction_w,
      nw: direction_nw,
      ne: direction_ne,
      sw: direction_sw,
      se: direction_se
    }),

    directions = direction_callbacks.keys()

    function direction_n() {
      var bbox = getScreenBBox()
      return {
        top:  bbox.n.y - node.offsetHeight,
        left: bbox.n.x - node.offsetWidth / 2
      }
    }

    function direction_s() {
      var bbox = getScreenBBox()
      return {
        top:  bbox.s.y,
        left: bbox.s.x - node.offsetWidth / 2
      }
    }

    function direction_e() {
      var bbox = getScreenBBox()
      return {
        top:  bbox.e.y - node.offsetHeight / 2,
        left: bbox.e.x
      }
    }

    function direction_w() {
      var bbox = getScreenBBox()
      return {
        top:  bbox.w.y - node.offsetHeight / 2,
        left: bbox.w.x - node.offsetWidth
      }
    }

    function direction_nw() {
      var bbox = getScreenBBox()
      return {
        top:  bbox.nw.y - node.offsetHeight,
        left: bbox.nw.x - node.offsetWidth
      }
    }

    function direction_ne() {
      var bbox = getScreenBBox()
      return {
        top:  bbox.ne.y - node.offsetHeight,
        left: bbox.ne.x
      }
    }

    function direction_sw() {
      var bbox = getScreenBBox()
      return {
        top:  bbox.sw.y,
        left: bbox.sw.x - node.offsetWidth
      }
    }

    function direction_se() {
      var bbox = getScreenBBox()
      return {
        top:  bbox.se.y,
        left: bbox.e.x
      }
    }

    function initNode() {
      var node = d3.select(document.createElement('div'))
      node.style({
        position: 'absolute',
        top: 0,
        opacity: 0,
        'pointer-events': 'none',
        'box-sizing': 'border-box'
      })

      return node.node()
    }

    function getSVGNode(el) {
      el = el.node()
      if(el.tagName.toLowerCase() === 'svg')
        return el

      return el.ownerSVGElement
    }

    // Private - gets the screen coordinates of a shape
    //
    // Given a shape on the screen, will return an SVGPoint for the directions
    // n(north), s(south), e(east), w(west), ne(northeast), se(southeast), nw(northwest),
    // sw(southwest).
    //
    //    +-+-+
    //    |   |
    //    +   +
    //    |   |
    //    +-+-+
    //
    // Returns an Object {n, s, e, w, nw, sw, ne, se}
    function getScreenBBox() {
      var targetel   = target || d3.event.target;

      while ('undefined' === typeof targetel.getScreenCTM && 'undefined' === targetel.parentNode) {
          targetel = targetel.parentNode;
      }

      var bbox       = {},
          matrix     = targetel.getScreenCTM(),
          tbbox      = targetel.getBBox(),
          width      = tbbox.width,
          height     = tbbox.height,
          x          = tbbox.x,
          y          = tbbox.y

      point.x = x
      point.y = y
      bbox.nw = point.matrixTransform(matrix)
      point.x += width
      bbox.ne = point.matrixTransform(matrix)
      point.y += height
      bbox.se = point.matrixTransform(matrix)
      point.x -= width
      bbox.sw = point.matrixTransform(matrix)
      point.y -= height / 2
      bbox.w  = point.matrixTransform(matrix)
      point.x += width
      bbox.e = point.matrixTransform(matrix)
      point.x -= width / 2
      point.y -= height / 2
      bbox.n = point.matrixTransform(matrix)
      point.y += height
      bbox.s = point.matrixTransform(matrix)

      return bbox
    }

    return tip
  };

}));

load_datapackage.py

import csv
import json
import operator

datapackage = json.loads(open('datapackage.json').read())


def row2header(row):
    header = {}
    j = 0
    for h in row:
        header[h] = j
        j = j + 1
    return header

def row2data(row,header):
    out = {}
    j = 0
    for h in header:
        out[h] = row[header[h]]
    return out

# extract info from datapackage
dp = {}
for resource in datapackage['resources']:
    if resource["name"] == "people":
        dp["people"] = resource
    if resource["name"] == "vote_events":
        dp["vote_events"] = resource
    if resource["name"] == "votes":
        dp["votes"] = resource

# load into variable
headers = {}
data = {"people":[],"votes":[],"vote_events":[]}
for key in data:
    i = 0
    with open(dp[key]["path"], 'r') as csvfile:
        csvreader = csv.reader(csvfile)
        for row in csvreader:
            if i == 0:
                headers[key] = row2header(row)
            else:
                data[key].append(row2data(row,headers[key]))
            i = i + 1
# reorder for easier access:
people = {}
for person in data["people"]:
    people[person['identifier']] = person
vote_events = {}
for vote_event in data["vote_events"]:
    vote_events[vote_event['identifier']] = vote_event
    
# sort votes
data["votes"] = sorted(data["votes"],key=operator.itemgetter("vote_event_id","voter_id"))

praha-2010-2014-roll-call-votes-wnominate.csv

identifier,name,party_suggesting,wnominate:d1,wnominate:d2,class
3167,Jan Slezák,ČSSD,0.844365298748,-0.475551307201,cssd
3169,Milan Urban,TOP 09,-0.847399175167,-0.141846179962,top-09
3184,Pavel Hurda,ODS,0.475262463093,0.576651632786,ods
3186,Ondřej Pecha,ODS,0.458752334118,0.538636863232,ods
3230,Marie Kousalíková,ODS,0.536619365215,0.43625587225,ods
3337,Josef Nosek,ODS,0.552258729935,0.653618454933,ods
3464,Pavel Klega,ODS,0.58333581686,0.480701893568,ods
3472,Miloslav Ludvík,ČSSD,0.867789626122,-0.436195641756,cssd
3478,Miroslav Poche,ČSSD,0.849666297436,-0.527320802212,cssd
3492,František Adámek,ČSSD,0.851758241653,-0.389698952436,cssd
3762,Marta Semelová,KSČM,0.913647353649,-0.217486530542,kscm
3926,Antonín Weinert,ČSSD,0.825632989407,-0.48580211401,cssd
3967,Karel Březina,ČSSD,0.847999513149,-0.444134861231,cssd
4014,Rudolf Blažek,ODS,0.763845920563,0.556767642498,ods
4041,Petr Hulinský,ČSSD,0.860926926136,-0.497503489256,cssd
4048,Radek Lohynský,ODS,0.418140858412,0.675759851933,ods
4065,Dalibor Mlejnský,ODS,0.57928019762,0.282766759396,ods
4085,Daniel Hodek,ČSSD,0.833508431911,-0.446265757084,cssd
4093,Petr Šimůnek,KSČM,0.877711713314,-0.239676296711,kscm
4306,Petr Bříza,ODS,0.509833693504,0.513472616673,ods
4307,Monika Krobová Hášová,TOP 09,-0.854757726192,-0.257377713919,top-09
4308,Albert Kubišta,TOP 09,-0.945103883743,-0.0787498727441,top-09
4309,Lubomír Ledl,KSČM,0.911291837692,-0.185980215669,kscm
4310,Jiří Pařízek,TOP 09,-0.910431981087,-0.140326648951,top-09
4311,Roman Petrus,ČSSD,0.861748456955,-0.503026068211,cssd
4312,Lukáš Plachý,ČSSD,0.857482492924,-0.465514570475,cssd
4313,Ondřej Počarovský,TOP 09,-0.901716709137,-0.227473780513,top-09
4314,Pavel Richter,TOP 09,-0.913848996162,0.0448365844786,top-09
4315,Vladimír Schmalz,ODS,0.534595668316,0.381742298603,ods
4316,Bohuslav Svoboda,ODS,0.566491723061,0.75400698185,ods
4317,Michal Štěpán,TOP 09,-0.967347860336,-0.173068419099,top-09
4318,Nataša Šturmová,TOP 09,-0.909221410751,-0.0676190182567,top-09
4319,Věra Šturmová,TOP 09,-0.901336550713,-0.0591023527086,top-09
4320,Ludmila Štvánová,TOP 09,-0.85670620203,-0.101598240435,top-09
4321,Lenka Teska Arnoštová,ČSSD,0.890404760838,-0.405343979597,cssd
4322,Petr Trombik,TOP 09,-0.865648150444,-0.180411010981,top-09
4323,Zdeněk Tůma,TOP 09,-0.83565813303,-0.0728031247854,top-09
4324,Andrea Vlásenková,ODS,0.420827120543,0.304606795311,ods
4325,Eva Vorlíčková,TOP 09,-0.999399662018,-0.0193237327039,top-09
4339,Zuzana Bonhomme Hankeová,TOP 09,-0.847445130348,-0.108895994723,top-09
4340,Iveta Borská,TOP 09,-0.903543591499,-0.100302673876,top-09
4341,Jiří Dienstbier,ČSSD,0.858711361885,-0.312924027443,cssd
4342,Martin Dlouhý,TOP 09,-0.879456400871,-0.0642564818263,top-09
4343,Petr Dodal,TOP 09,-0.825085878372,-0.132989287376,top-09
4344,Petr Dolínek,ČSSD,0.863325178623,-0.504648208618,cssd
4345,Milan Růžička,TOP 09,-0.905564665794,-0.137348085642,top-09
4346,Petr Hána,ODS,0.712317824364,0.701857089996,ods
4347,Tomáš Hudeček,TOP 09,-0.996660590172,0.0816562920809,top-09
4348,Helena Chudomelová,TOP 09,-0.833634972572,-0.0807498544455,top-09
4349,Gabriela Kloudová,ODS,0.725271046162,0.193118587136,ods
4350,Alexandra Udženija,ODS,0.427044451237,0.608168005943,ods
4351,Jan Vašíček,TOP 09,-0.89971780777,-0.107256688178,top-09
4352,Jiří Vávra,TOP 09,-0.898380100727,-0.156385675073,top-09
4353,David Zelený,ODS,0.762448370457,0.6470490098,ods
4369,Ivan Kabický,ODS,0.408881902695,0.44592782855,ods
4370,Jan Kalousek,ODS,0.484505742788,0.874788165092,ods
4371,Lukáš Kaucký,ČSSD,0.865863323212,-0.474781185389,cssd
4372,Jiří Liška,TOP 09,-0.832347452641,-0.0914602428675,top-09
4373,Lukáš Manhart,TOP 09,-0.887736558914,0.0477734580636,top-09
4374,Jiří Nouza,TOP 09,-0.8783390522,-0.152244329453,top-09
4375,Václav Novotný,TOP 09,-0.990503966808,-0.13748383522,top-09
4683,Boris Šťastný,ODS,0.707905709743,0.142590999603,ods
4684,Milan Richter,ODS,0.699684739113,0.20471675694,ods
4355,Roman Vaculka,TOP 09,-0.894880175591,-0.041933003813,top-09
4624,Lucie Válová,ČSSD,0.72899967432,-0.68451410532,cssd
3484,Bohumil Zoufalík,ODS,0.974843025208,0.222892835736,ods
4216,Ivan Vinš,ODS,0.902668476105,0.430336415768,ods

praha-2010-2014-roll-call-votes-wpca.csv

identifier,name,party_suggesting,wpca:d1,wpca:d2,wpca:d3,class
3167,Jan Slezák,ČSSD,14.9448913417,8.91394878397,-3.21591231859,cssd
3169,Milan Urban,TOP 09,-15.3748324175,1.11455291993,0.44803846422,top-09
3184,Pavel Hurda,ODS,9.12297995397,-9.76884276446,-2.39501339437,ods
3186,Ondřej Pecha,ODS,8.69567121532,-10.2378197542,-2.92427933734,ods
3230,Marie Kousalíková,ODS,9.40727040416,-8.20110944296,-1.95316085614,ods
3337,Josef Nosek,ODS,8.77587623575,-11.4494284611,0.850965415615,ods
3464,Pavel Klega,ODS,12.2114585445,-7.74100841675,-0.99193327788,ods
3472,Miloslav Ludvík,ČSSD,14.8652343415,9.46064423766,-2.05723820701,cssd
3478,Miroslav Poche,ČSSD,14.8596172767,10.0210779125,-2.14372491869,cssd
3492,František Adámek,ČSSD,14.5024074131,8.73706190798,-1.70622520893,cssd
3762,Marta Semelová,KSČM,6.70810009286,8.38799647887,18.2859774319,kscm
3926,Antonín Weinert,ČSSD,14.5166529089,8.16301067442,-3.27060368418,cssd
3967,Karel Březina,ČSSD,14.6722047046,8.86955896582,-2.49140799807,cssd
4014,Rudolf Blažek,ODS,14.0756781942,-7.57277224975,1.79203632159,ods
4041,Petr Hulinský,ČSSD,14.6165725807,8.48349548493,-3.46141454684,cssd
4048,Radek Lohynský,ODS,9.26097574529,-11.4755028488,-0.815309815961,ods
4065,Dalibor Mlejnský,ODS,12.3475140963,-7.37565857618,0.32402657255,ods
4085,Daniel Hodek,ČSSD,14.5856286848,9.24372564465,-2.6552106916,cssd
4093,Petr Šimůnek,KSČM,5.64457615356,7.81071411201,18.0761207454,kscm
4306,Petr Bříza,ODS,8.52268814436,-10.0034727305,-2.69095279661,ods
4307,Monika Krobová Hášová,TOP 09,-15.7385045648,1.19709678227,0.592001326003,top-09
4308,Albert Kubišta,TOP 09,-16.7535382329,0.0208135523753,-0.701271562167,top-09
4309,Lubomír Ledl,KSČM,7.40973632033,7.58344687105,16.5903965215,kscm
4310,Jiří Pařízek,TOP 09,-16.1592931596,0.535578730618,-0.287090270965,top-09
4311,Roman Petrus,ČSSD,14.9516971752,9.86404829653,-2.19871101468,cssd
4312,Lukáš Plachý,ČSSD,14.8901522218,9.22546208082,-2.19139248475,cssd
4313,Ondřej Počarovský,TOP 09,-15.4120243793,0.715862557805,0.331802354634,top-09
4314,Pavel Richter,TOP 09,-15.964990418,-0.249981477982,-0.331502564035,top-09
4315,Vladimír Schmalz,ODS,11.5786064452,-8.71689840299,-0.0598377011954,ods
4316,Bohuslav Svoboda,ODS,7.16721325286,-10.8514839467,-2.50328233146,ods
4317,Michal Štěpán,TOP 09,-16.4127429929,0.898065918316,-0.141708885424,top-09
4318,Nataša Šturmová,TOP 09,-16.0413535321,0.10115952636,-0.764436585476,top-09
4319,Věra Šturmová,TOP 09,-16.307839438,0.487318373523,-0.316679084316,top-09
4320,Ludmila Štvánová,TOP 09,-15.5486690248,-0.190775443673,0.16079568745,top-09
4321,Lenka Teska Arnoštová,ČSSD,15.5557054536,8.51455748663,-1.1819797874,cssd
4322,Petr Trombik,TOP 09,-15.2692992527,1.25352992483,-0.0972574959438,top-09
4323,Zdeněk Tůma,TOP 09,-15.5794332835,0.557293380133,-0.0294558496958,top-09
4324,Andrea Vlásenková,ODS,8.00885693441,-9.36684457483,-2.31562372631,ods
4325,Eva Vorlíčková,TOP 09,-16.6728192875,0.0311663333347,0.294848227939,top-09
4339,Zuzana Bonhomme Hankeová,TOP 09,-15.3929855567,0.488144192104,0.653984236363,top-09
4340,Iveta Borská,TOP 09,-16.2349424056,1.11885077298,0.681336742804,top-09
4341,Jiří Dienstbier,ČSSD,11.2962677103,3.76507800902,-1.33270907171,cssd
4342,Martin Dlouhý,TOP 09,-16.1170059939,0.246043657009,-0.55134444397,top-09
4344,Petr Dolínek,ČSSD,14.7636760725,10.0461686607,-2.15977390887,cssd
4345,Milan Růžička,TOP 09,-15.8920771975,1.14315550434,0.727702750821,top-09
4346,Petr Hána,ODS,10.8872860406,-10.6601958938,0.236143694465,ods
4347,Tomáš Hudeček,TOP 09,-15.8916389329,-0.245730574682,-0.345553103284,top-09
4348,Helena Chudomelová,TOP 09,-14.8498246746,-0.0536473750414,-0.474812617157,top-09
4349,Gabriela Kloudová,ODS,14.7872145271,-3.91221886652,1.90038086045,ods
4350,Alexandra Udženija,ODS,8.16489704147,-10.3164376104,-2.55662574516,ods
4351,Jan Vašíček,TOP 09,-15.6949354549,0.742474861707,-0.451051207601,top-09
4352,Jiří Vávra,TOP 09,-15.3049736252,0.279939754848,-0.0798532725172,top-09
4353,David Zelený,ODS,12.2821781252,-7.98482174105,1.2173557931,ods
4369,Ivan Kabický,ODS,7.7193601371,-10.692439176,-2.96857736875,ods
4370,Jan Kalousek,ODS,7.94847479804,-11.9667893598,-0.883101622919,ods
4371,Lukáš Kaucký,ČSSD,15.0789402937,9.9056567827,-2.88065715213,cssd
4372,Jiří Liška,TOP 09,-14.9518414778,0.282122606948,0.342636631861,top-09
4373,Lukáš Manhart,TOP 09,-15.7859964626,-0.132459189767,-0.592785426162,top-09
4374,Jiří Nouza,TOP 09,-15.4100148016,0.740625720025,-0.165395433852,top-09
4375,Václav Novotný,TOP 09,-16.5745741837,0.205251168609,0.132878559258,top-09
4683,Boris Šťastný,ODS,13.0687938424,-1.75014720205,-6.79050669296,ods
4684,Milan Richter,ODS,14.4838955498,-0.307268368976,-6.54179693875,ods
4355,Roman Vaculka,TOP 09,-15.9777513845,-0.492901202876,-1.18178113292,top-09
4624,Lucie Válová,ČSSD,9.47309594441,17.3746135201,1.95379814233,cssd
3484,Bohumil Zoufalík,ODS,12.503735192,-11.0612380211,14.6245438748,ods
4216,Ivan Vinš,ODS,12.9935496716,-13.3179239811,15.0955134406,ods

prepare_matrix.r

Xsource = matrix(Xsourcevector,ncol=3,byrow=T)
dimnames(Xsource)[[2]] = c('vote_event_id','voter_id','option')
Xsource = as.data.frame(Xsource)

wnominate.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>Scatterplot Chart</title>
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta name="description" content="scatter plot">
    <meta name="author" content="Michal Škop">
    <script src="//d3js.org/d3.v3.min.js"></script>
    <script src="./d3.scatterplot.js"></script>
    <script src="./d3.tips.js"></script>
    <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootswatch/3.2.0/simplex/bootstrap.min.css" />
    <style type="text/css">
			
			text {
			  font-family: sans-serif;
			}
			
			.tick {
			  fill-opacity: 0;
			  stroke: #000000;
			  stroke-width: 1;
			}
			
			.domain {
			    fill: none;
				fill-opacity: 0;
				stroke: black;
				stroke-width: 1;
			}
			.axis line {
				fill: none;
				fill-opacity: 0;
				stroke: black;
				stroke-width: 1;
				shape-rendering: crispEdges;
			}
			
			.axis text {
				font-family: sans-serif;
				font-size: 11px;
			}
			.axis {
			  
			}
			circle {
			  fill-opacity: .3;
			  stroke: black;
			  stroke-opacity: 0.99;
			}
			circle:hover {
			  fill-opacity: 1;
			}
			.year-2011 {
			  fill: #000;
			  stroke: #000;
			}
			.year-2013 {
			  fill: #fff;
			  fill-opacity: 0;
			  stroke: #000;
			}
			.label {
			  font-family: sans-serif;
			  font-size: 15px;
			}
			.cssd {
			  fill: orange;
			  stroke: orange;
			}
			.kscm {
			  fill: red;
			  stroke: red;
			}
			.ods {
			  fill: blue;
			  stroke: blue;
			}
			.top-09 {
			  fill: violet;
			  stroke: violet;
			}
			.d3-tip {
                line-height: 1;
                font-weight: bold;
                padding: 12px;
                background: rgba(0, 0, 0, 0.8);
                color: #fff;
                border-radius: 2px;
                pointer-events: none;
            }
            /* Creates a small triangle extender for the tooltip */
            .d3-tip:after {
                box-sizing: border-box;
                display: inline;
                font-size: 10px;
                width: 100%;
                line-height: 1;
                color: rgba(0, 0, 0, 0.8);
                position: absolute;
                pointer-events: none;
            }
            /* Northward tooltips */
            .d3-tip.n:after {
                content: "\25BC";
                margin: -1px 0 0 0;
                top: 100%;
                left: 0;
                text-align: center;
            }
            /* Eastward tooltips */
.d3-tip.e:after {
content: "\25C0";
margin: -4px 0 0 0;
top: 50%;
left: -8px;
}
/* Southward tooltips */
.d3-tip.s:after {
content: "\25B2";
margin: 0 0 1px 0;
top: -8px;
left: 0;
text-align: center;
}
/* Westward tooltips */
.d3-tip.w:after {
content: "\25B6";
margin: -4px 0 0 -1px;
top: 50%;
left: 100%;
}

		</style>
  </head>
  <body>
    <div class="navbar navbar-default">
      <div class="navbar-header">
       <h1 class="navbar-brand">Prague 2010 - 2014</h1>
      </div>
    </div>
    <div id="chart"></div>
    <div class="alert alert-dismissable alert-info">
      WNominate
    </div>
    
<script type="text/javascript">
  d3.csv("praha-2010-2014-roll-call-votes-wnominate.csv",function(error,csvdata) {
    spdata = [];
    csvdata.forEach(function(d) {
      spdata.push({"x":d["wnominate:d1"],"y":-d["wnominate:d2"],"r":1,"class":d["class"],"name":d["name"]})
    });
    var scatterplot = [{
      "data": spdata,
      "margin": {top: 10, right: 10, bottom: 30, left: 30},
      "axes": {"labels":{"x":"Dimension 1", "y":"Dimension 2"}},
      "minmax":{"x":{'min':-1,'max':1},"y":{'min':-1,'max':1},"r":{'min':0,'max':1},"rrange":{'min':0,'max':10}},
      "size":{"width":600,"height":400}
    }];
    
     var svg = d3.select("#chart")
        .append("svg")
        .attr("width",scatterplot[0]['size']['width'])
        .attr("height",scatterplot[0]['size']['height']);
        
    /* Initialize tooltip */
    tip = d3.tip().attr('class', 'd3-tip').html(function(d) { 
      return "<span class=\'stronger\'>" + d["name"] + "</span><br>";
    });

    /* Invoke the tip in the context of your visualization */
    svg.call(tip)
    
    var sp = d3.scatterplot()
        .data(function(d) {return d.data})
        .margin(function(d) {return d.margin})
        .axes(function(d) {return d.axes})
        .minmax(function(d) {return d.minmax})
        .size(function(d) {return d.size})
    
    var scatter = svg.selectAll(".scatterplot")
        .data(scatterplot)
      .enter()
        .append("svg:g")
        .attr("transform", "translate(" + scatterplot[0].margin.left + "," + scatterplot[0].margin.top + ")")
        .call(sp);
    
  });
</script>
  </body>
</html>

wnominate.py

import csv
import json
import operator
import numpy
import rpy2.robjects as robjects
r=robjects.r

exec(open("load_datapackage.py").read())

# load into row vector in R
Xsourcevector = []
for row in data["votes"]:
    Xsourcevector.append(row["vote_event_id"])
    Xsourcevector.append(row["voter_id"])
    Xsourcevector.append(row["option"])
    
robjects.globalenv["Xsourcevector"] = robjects.StrVector(Xsourcevector)
r.source("prepare_matrix.r")

# calculate WNominate
r.source("wnominate_script.r")

#save WNominate
with open(datapackage['name']+'-wnominate.csv', 'w') as csvfile:
    csvwriter = csv.writer(csvfile)
    csvwriter.writerow(["identifier","name","candidate_list","wnominate:d1","wnominate:d2"])
    rWNpositions = numpy.array(robjects.globalenv['WNpositions'])
    rXpeople = robjects.globalenv['Xpeople']
    rXvote_events = robjects.globalenv['Xvote_events']
    i = 0
    for row in rWNpositions:
        if row[0] != 'NA':
            csvwriter.writerow([people[rXpeople[i]]['identifier'],people[rXpeople[i]]['name'],people[rXpeople[i]]['candidate_list'],row[0],row[1]])
        i = i + 1

wnominate_script.r

# INPUT PARAMETERS
# _X_RAW_DB, _LO_LIMIT_1
# raw data in csv using db structure, i.e., a single row contains:
# code of representative, code of division, encoded vote (i.e. one of -1, 1, 0, NA)
# for example:
# “Joe Europe”,”Division-007”,”1”

#Xsource = read.csv("data/votes.csv")

Xsource$vote_event_id = as.factor(Xsource$vote_event_id)
Xsource$voter_id = as.factor(Xsource$voter_id)

Xsource$option_numeric = rep(0,length(Xsource$option))
Xsource$option_numeric[Xsource$option=='yes'] = 1
Xsource$option_numeric[Xsource$option=='no'] = -1
Xsource$option_numeric[Xsource$option=='abstain'] = -1
Xsource$option_numeric[Xsource$option=='not voting'] = NA
Xsource$option_numeric[Xsource$option=='absent'] = NA
Xsource$option_numeric = as.numeric(Xsource$option_numeric)

#Xrawdb = _X_RAW_DB
Xrawdb = Xsource

# reorder data; divisions x persons
# we may need to install and/or load some additional libraries
# install.packages("reshape2")
 library("reshape2")
# install.packages("sqldf")
# library("sqldf")

#prevent reordering, which is behaviour of acast:
#Xrawdb$V1 = factor(Xrawdb$V1, levels=unique(Xrawdb$V1))
Xrawdb$voter_id = factor(Xrawdb$voter_id, levels=unique(Xrawdb$voter_id))
Xraw = acast(Xrawdb,voter_id~vote_event_id,value.var='option_numeric')
Xpeople = dimnames(Xraw)[[1]]
Xvote_events = dimnames(Xraw)[[2]]
WNraw=apply(Xraw,2,as.numeric)   #different from WPCA
dimnames(WNraw)[[1]] = Xpeople

#wnominate
library("wnominate")
wn = rollcall(WNraw,yea=1,nay=-1,missing=c(0,NA))
wnr = wnominate(wn,polarity=c(1,1))
#summary(wnr)
#plot(wnr)
WNpositions = cbind(wnr$legislators$coord1D,wnr$legislators$coord2D)

wpca.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>Scatterplot Chart</title>
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta name="description" content="scatter plot">
    <meta name="author" content="Michal Škop">
    <script src="//d3js.org/d3.v3.min.js"></script>
    <script src="./d3.scatterplot.js"></script>
    <script src="./d3.tips.js"></script>
    <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootswatch/3.2.0/simplex/bootstrap.min.css" />
    <style type="text/css">
			
			text {
			  font-family: sans-serif;
			}
			
			.tick {
			  fill-opacity: 0;
			  stroke: #000000;
			  stroke-width: 1;
			}
			
			.domain {
			    fill: none;
				fill-opacity: 0;
				stroke: black;
				stroke-width: 1;
			}
			.axis line {
				fill: none;
				fill-opacity: 0;
				stroke: black;
				stroke-width: 1;
				shape-rendering: crispEdges;
			}
			
			.axis text {
				font-family: sans-serif;
				font-size: 11px;
			}
			.axis {
			  
			}
			circle {
			  fill-opacity: .3;
			  stroke: black;
			  stroke-opacity: 0.99;
			}
			circle:hover {
			  fill-opacity: 1;
			}
			.year-2011 {
			  fill: #000;
			  stroke: #000;
			}
			.year-2013 {
			  fill: #fff;
			  fill-opacity: 0;
			  stroke: #000;
			}
			.label {
			  font-family: sans-serif;
			  font-size: 15px;
			}
			.cssd {
			  fill: orange;
			  stroke: orange;
			}
			.kscm {
			  fill: red;
			  stroke: red;
			}
			.ods {
			  fill: blue;
			  stroke: blue;
			}
			.top-09 {
			  fill: violet;
			  stroke: violet;
			}
			.d3-tip {
                line-height: 1;
                font-weight: bold;
                padding: 12px;
                background: rgba(0, 0, 0, 0.8);
                color: #fff;
                border-radius: 2px;
                pointer-events: none;
            }
            /* Creates a small triangle extender for the tooltip */
            .d3-tip:after {
                box-sizing: border-box;
                display: inline;
                font-size: 10px;
                width: 100%;
                line-height: 1;
                color: rgba(0, 0, 0, 0.8);
                position: absolute;
                pointer-events: none;
            }
            /* Northward tooltips */
            .d3-tip:after {
                content: "\25BC";
                margin: -1px 0 0 0;
                top: 100%;
                left: 0;
                text-align: center;
            }

		</style>
  </head>
  <body>
    <div class="navbar navbar-default">
      <div class="navbar-header">
       <h1 class="navbar-brand">Prague 2010 - 2014</h1>
      </div>
    </div>
    <div id="chart"></div>
    <div class="alert alert-dismissable alert-info">
      WPCA
    </div>
    
<script type="text/javascript">
  d3.csv("praha-2010-2014-roll-call-votes-wpca.csv",function(error,csvdata) {
    spdata = [];
    csvdata.forEach(function(d) {
      spdata.push({"x":d["wpca:d1"],"y":d["wpca:d2"],"r":1,"class":d["class"],"name":d["name"]})
    });
    var scatterplot = [{
      "data": spdata,
      "margin": {top: 10, right: 10, bottom: 30, left: 30},
      "axes": {"labels":{"x":"Dimension 1", "y":"Dimension 2"}},
      "minmax":{"x":{'min':-18,'max':18},"y":{'min':-18,'max':18},"r":{'min':0,'max':1},"rrange":{'min':0,'max':10}},
      "size":{"width":600,"height":400}
    }];
    
     var svg = d3.select("#chart")
        .append("svg")
        .attr("width",scatterplot[0]['size']['width'])
        .attr("height",scatterplot[0]['size']['height']);
        
    /* Initialize tooltip */
    tip = d3.tip().attr('class', 'd3-tip').html(function(d) { 
      return "<span class=\'stronger\'>" + d["name"] + "</span><br>";
    });

    /* Invoke the tip in the context of your visualization */
    svg.call(tip)
    
    var sp = d3.scatterplot()
        .data(function(d) {return d.data})
        .margin(function(d) {return d.margin})
        .axes(function(d) {return d.axes})
        .minmax(function(d) {return d.minmax})
        .size(function(d) {return d.size})
    
    var scatter = svg.selectAll(".scatterplot")
        .data(scatterplot)
      .enter()
        .append("svg:g")
        .attr("transform", "translate(" + scatterplot[0].margin.left + "," + scatterplot[0].margin.top + ")")
        .call(sp);
    
  });
</script>
  </body>
</html>

wpca.py

import csv
import json
import operator
import numpy
import rpy2.robjects as robjects
r=robjects.r

exec(open("load_datapackage.py").read())

# load into row vector in R
Xsourcevector = []
for row in data["votes"]:
    Xsourcevector.append(row["vote_event_id"])
    Xsourcevector.append(row["voter_id"])
    Xsourcevector.append(row["option"])
    
robjects.globalenv["Xsourcevector"] = robjects.StrVector(Xsourcevector)
r.source("prepare_matrix.r")

# calculate WPCA
r.source("wpca_script.r")
#save WPCA
with open(datapackage['name']+'-wpca.csv', 'w') as csvfile:
    csvwriter = csv.writer(csvfile)
    csvwriter.writerow(["identifier","name","candidate_list","wpca:d1","wpca:d2","wpca:d3"])
    i = 0   # all people
    k = 0   # cutted people
    rXproj = numpy.array(robjects.globalenv['Xproj'])
    rXpeople = robjects.globalenv['Xpeople']
    rXvote_events = robjects.globalenv['Xvote_events']
    for item in robjects.globalenv['pI']:
        if item:
            csvwriter.writerow([people[rXpeople[i]]['identifier'],people[rXpeople[i]]['name'],people[rXpeople[i]]['candidate_list'],rXproj[k,0],rXproj[k,1],rXproj[k,2]])
            k = k + 1 
        i = i + 1

wpca_cutting_lines.py

# run only after wpca.py

# CUTTING LINES
r.source("wpca_cutting_lines_script.r")

rcl = numpy.array(robjects.globalenv['cl'])

out = []
with open(datapackage['name']+'-wpca-cutting-lines.csv', 'w') as csvfile:
    csvwriter = csv.writer(csvfile)
    csvwriter.writerow(["normal_x","normal_y","cl_beta0","loss","w1","w2","identifier","motion:name","start_date","result"])
    i = 0   # all vote events
    for ve in rcl:
        out.append(list(rcl[i]) + [vote_events[rXvote_events[i]]["identifier"],vote_events[rXvote_events[i]]["motion:name"],vote_events[rXvote_events[i]]["start_date"],vote_events[rXvote_events[i]]["result"]])
        csvwriter.writerow(out[i])
        i = i + 1

wpca_cutting_lines_script.r

# CUTTING LINES
# additional parameters:
# _N_FIRST_DIMENSIONS
# how many dimensions?
n_first_dimensions = 2

# loss function
LF = function(beta0) -1*sum(apply(cbind(y*(x%*%beta+beta0),zeros),1,min))

# preparing variables
normals = Xy[,1:n_first_dimensions]
loss_f = data.frame(matrix(0,nrow=dim(X)[1],ncol=4))
colnames(loss_f)=c("Parameter1","Loss1","Parameter_1","Loss_1")
parameters = data.frame(matrix(0,nrow=dim(X)[1],ncol=3))
colnames(parameters)=c("Parameter","Loss","Direction")

xfull = t(t(Xe$vectors[,1:n_first_dimensions]) * sqrt(Xe$values[1:n_first_dimensions]))

#calculating all cutting lines
for (i in as.numeric(1:dim(X)[1])) {
    beta = Xy[i,1:n_first_dimensions]
    y = t(as.matrix(X[i,]))[,pI]
    x = xfull[which(!is.na(y)),]
    y = y[which(!is.na(y))]
    zeros = as.matrix(rep(0,length(y)))
    # note: “10000” should be enough for any real-life case:
    res1 = optim(c(1),LF,method="Brent",lower=-10000,upper=10000)        
# note: the sign is arbitrary, the real result may be -1*; we need to minimize the other way as well
    y=-y
    res2 = optim(c(1),LF,method="Brent",lower=-10000,upper=10000) 

    # the real parameter is the one with lower loss function
    # note: theoretically should be the same (either +1 or -1) for all divisions(rows), however, due to missing data, the calculation may lead to a few divisions with the other direction
    loss_f[i,] = c(res1$par,res1$value,res2$par,res2$value)
    if (res1$value<=res2$value) {
      parameters[i,] = c(res1$par,res1$value,1)
    } else {
      parameters[i,] = c(res2$par,res2$value,-1)
    }
}
CuttingLines = list(normals=normals,parameters=parameters,loss_function=LF,weights=cbind(w1,w2))
cl = cbind(CuttingLines$normals,CuttingLines$parameters$Parameter,CuttingLines$parameters$Loss,w1,w2)

wpca_projection_script.r

# PROJECTION OF PART OF THE LEGISLATIVE TERM INTO THE WHOLE TERM MODEL
# additional parameters:
# _TI,   _LO_LIMIT_2
# lower limit to eliminate from projections (may be the same as lo_limit); number
 lo_limitT = .1
# indices whether divisions are in the given part or not
# vector of length div, contains TRUE or FALSE
# TI = _TI
TI = as.logical(TI)
# scaling, weighting and missing values treatment
XrawTc = Xraw[,pI]
XrawTc[!TI,] = NA
XTc = (XrawTc - attr(Xstand,"scaled:center"))/attr(Xstand,"scaled:scale")
# Indices of NAs; division x person
TIc = XTc
TIc[!is.na(TIc)] = 1
TIc[is.na(TIc)] = 0
# weights, divisions x persons
XTw0c = XTc * w1 * w2
XTw0c[is.na(XTw0c)] = 0

# weights for non missing data; divisions x persons
TIcw = TIc*w1*w2
# sum of weights of divisions for each persons
s = apply(TIcw,2,sum)
pTw = s/max(s)
# index of persons in calculation
pTI = pTw > lo_limitT

# weighted scaled with NA->0 and cutted persons with too few votes division x persons
XTw0cc = XTw0c[,pTI]
# index of missing data cutted persons with too few votes divisions x persons
TIcc = TIc[,pTI]

# weights of divisions, person x division
aZ = abs(Z)
dweights = t(t(aZ)%*%TIcc / apply(aZ,2,sum))      
dweights[is.na(dweights)] = 0

# projection of persons
XTw0ccproj = t(XTw0cc)%*%Z / dweights

# analytical chart:
plot(XTw0ccproj[,1],XTw0ccproj[,2])

wpca_script.r

# INPUT PARAMETERS
# _X_RAW_DB, _LO_LIMIT_1
# raw data in csv using db structure, i.e., a single row contains:
# code of representative, code of division, encoded vote (i.e. one of -1, 1, 0, NA)
# for example:
# “Joe Europe”,”Division-007”,”1”


#Xsource = read.csv("data/votes.csv")

Xsource$vote_event_id = as.factor(Xsource$vote_event_id)
Xsource$voter_id = as.factor(Xsource$voter_id)
#Xsource = Xsource[c("voter_id","vote_event_id","option")]
Xsource$option_numeric = rep(0,length(Xsource$option))
Xsource$option_numeric[Xsource$option=='yes'] = 1
Xsource$option_numeric[Xsource$option=='no'] = -1
Xsource$option_numeric[Xsource$option=='abstain'] = -1
Xsource$option_numeric[Xsource$option=='not voting'] = NA
Xsource$option_numeric[Xsource$option=='absent'] = NA
Xsource$option_numeric = as.numeric(Xsource$option_numeric)

#Xrawdb = _X_RAW_DB
Xrawdb = Xsource
# lower limit to eliminate from calculations, e.g., .1; number
lo_limit = .1

# reorder data; divisions x persons
# we may need to install and/or load some additional libraries
# install.packages("reshape2")
 library("reshape2")
# install.packages("sqldf")
# library("sqldf")

#prevent reordering, which is behaviour of acast:
#Xrawdb$V1 = factor(Xrawdb$V1, levels=unique(Xrawdb$V1))
Xrawdb$voter_id = factor(Xrawdb$voter_id, levels=unique(Xrawdb$voter_id))
Xraw = acast(Xrawdb,voter_id~vote_event_id,value.var='option_numeric')
Xpeople = dimnames(Xraw)[[1]]
Xvote_events = dimnames(Xraw)[[2]]
Xraw=apply(Xraw,1,as.numeric)
# scale data; divisions x persons (mean=0 and sd=1 for each division)
Xstand=t(scale(t(Xraw),scale=TRUE))

# WEIGHTS
# weights 1 for divisions, based on number of persons in division
w1 = apply(abs(Xraw)==1,1,sum,na.rm=TRUE)/max(apply(abs(Xraw)==1,1,sum,na.rm=TRUE))
w1[is.na(w1)] = 0
# weights 2 for divisions, "100:100" vs. "195:5"
w2 = 1 - abs(apply(Xraw==1,1,sum,na.rm=TRUE) - apply(Xraw==-1,1,sum,na.rm=TRUE))/apply(!is.na(Xraw),1,sum)
w2[is.na(w2)] = 0

# analytical charts for weights:
#plot(w1)
#plot(w2)
#plot(w1*w2)

# weighted scaled matrix; divisions x persons
X = Xstand * w1 * w2

# MISSING DATA
# index of missing data; divisions x persons
I = X
I[!is.na(X)] = 1
I[is.na(X)] = 0

# weighted scaled with NA substituted by 0; division x persons
X0 = X
X0[is.na(X)]=0

# EXCLUSION OF REPRESENTATIVES WITH TOO FEW VOTES (WEIGHTED)
# weights for non missing data; division x persons
Iw = I*w1*w2
# sum of weights of divisions for each persons; vector of length “persons”
s = apply(Iw,2,sum)
pw = s/(t(w1)%*%w2)
# index of persons kept in calculation; vector of length “persons”
pI = pw > lo_limit
# weighted scaled with NA->0 and cutted persons with too few weighted votes; division x persons
X0c = X0[,pI]
# index of missing cutted (excluded) persons with too few weighted votes; divisions x persons
Ic = I[,pI]
# indexes of cutted (excluded) persons with too few votes; divisions x persons
Iwc = Iw[,pI]

# “X’X” MATRIX
# weighted X’X matrix with missing values substituted and excluded persons; persons x persons
C=t(X0c)%*%X0c * 1/(t(Iwc)%*%Iwc) * (sum(w1*w1*w2*w2))
# substitution of missing data in "covariance" matrix (the simple way)
C0 = C
C0[is.na(C)] = 0

# DECOMPOSITION
# eigendecomposition
Xe=eigen(C0)
# W (rotation values of persons)
W = Xe$vectors
# projected divisions into dimensions
Xy=X0c%*%W

# analytical charts of projection of divisions and lambdas
#plot(Xy[,1],Xy[,2])
#plot(sqrt(Xe$values[1:10]))

# lambda matrix
lambda = diag(sqrt(Xe$values))
lambda[is.na(lambda)] = 0

# projection of persons into dimensions
Xproj = W%*%lambda
    
# analytical chart
#plot(Xproj[,1],Xproj[,2])

# lambda^-1 matrix
lambda_1 = diag(sqrt(1/Xe$values))
lambda_1[is.na(lambda_1)] = 0

# Z (rotation values of divisions)
Z = X0c%*%W%*%lambda_1

# analytical charts
# second projection
Xproj2 = t(X0c) %*% Z
# without missing values, they are equal:
#plot(Xproj[,1],Xproj2[,1])
#plot(Xproj[,2],Xproj2[,2])