block by michalskop 634757c22d79eb462829

EU: Spatial model 2014-2015

Full Screen

index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>W-PCA Scatterplot Chart</title>
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta name="description" content="WPCA 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="https://maxcdn.bootstrapcdn.com/bootswatch/3.3.5/united/bootstrap.min.css">

    <style type="text/css">
			
			/* note: we duplicate some of the styles (css, and as attributes of svg elements), so FF displays it correctly, and it is possible to generate png */
			.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:hover {
			  fill-opacity: 1;
			}
			.label {
			  font-family: sans-serif;
			  font-size: 15px;
			}

			.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: 400px;
            }
            /* 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:0;
             opacity: .3;
            }
            .perfect {
              stroke: gray;
              stroke-width:0;
              opacity: 0.7;
            }
            #chart {
              padding-top:30px;
            }
		</style>
  </head>
  <body>
    <nav class="navbar navbar-default">
      <div class="container">
        <div class="navbar-header">
          <h2>Spatial distribution of MEPs based on their votes in the 1st year of the 8th term (2014-2015)</h2>
        </div>
      </div>
    </nav>
    <div id="chart"></div>
    <div class="alert alert-info">
      <p>Each bubble represents one MEP.
      <p><a href="//bl.ocks.org/michalskop/8514867">W-PCA</a> model is used.
      <p>Another file: c2.html (with cutting lines)

    
<script type="text/javascript">

d3.csv("chart_data.csv", function(voters) {
    //d3.csv("cutting_lines_ten.csv", function(lines) {
        /*linesselected = [];
        for (k in lines) {
          if ((parseFloat(lines[k]['loss']) < 1.5) && (parseFloat(lines[k]['cl_beta0']) < 50)) {
              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;
              }
              //add class for a perfect cut:
              if (lines[k]['loss'] == 0) {
                lines[k]['class'] = 'perfect';
              } else {
                lines[k]['class'] = 'non-perfect';
              }
              lines[k]['name'] = lines[k]["motion:name"];
              linesselected.push(lines[k]);
          }
        }*/

        spdata = [];
        voters.forEach(function(d) {
          spdata.push({"x":d["wpca:d1"],"y":d["wpca:d2"],"r":d["r"],"color":d["color"],"name":d["name"],"opacity":d["opacity"], "party": d["party"], "country": d["country"]})
        });
        var scatterplotwithlineplot = [{
          "data": spdata,
          "margin": {top: 10, right: 10, bottom: 30, left: 30},
          "axes": {"labels":{"x":"DIM 1", "y":"DIM 2"}},
          "minmax":{"x":{'min':-1,'max':1},"y":{'min':-1,'max':1},"r":{'min':0,'max':1},"rrange":{'min':0,'max':100}},
          "size":{"width":500,"height":500}//,
          //"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>" + d["party"] + "<br>" + d["country"];
        });

        /* 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>

c2.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>W-PCA Scatterplot Chart</title>
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta name="description" content="WPCA 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="https://maxcdn.bootstrapcdn.com/bootswatch/3.3.5/united/bootstrap.min.css">

    <style type="text/css">
			
			/* note: we duplicate some of the styles (css, and as attributes of svg elements), so FF displays it correctly, and it is possible to generate png */
			.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:hover {
			  fill-opacity: 1;
			}
			.label {
			  font-family: sans-serif;
			  font-size: 15px;
			}

			.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: 400px;
            }
            /* 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: .3;
            }
            .perfect {
              stroke: gray;
              stroke-width:2;
              opacity: 0.7;
            }
            #chart {
              padding-top:30px;
            }
		</style>
  </head>
  <body>
    <nav class="navbar navbar-default">
      <div class="container">
        <div class="navbar-header">
                    <h2>Spatial distribution of MEPs based on their votes in the 1st year of the 8th term (2014-2015)</h2>
        </div>
      </div>
    </nav>
    <div id="chart"></div>
    <div class="alert alert-info">
      <p>Each bubble represents one MEP.
      <p><a href="//bl.ocks.org/michalskop/8514867">W-PCA</a> model is used.

    
<script type="text/javascript">

d3.csv("chart_data.csv", function(voters) {
    d3.csv("cutting_lines.csv", function(lines) {
        linesselected = [];
        for (k in lines) {
          if ((parseFloat(lines[k]['loss']) < 33) && (parseFloat(lines[k]['cl_beta0']) < 50)) {
              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;
              }
              //add class for a perfect cut:
              if (lines[k]['loss'] == 0) {
                lines[k]['class'] = 'perfect';
              } else {
                lines[k]['class'] = 'non-perfect';
              }
              lines[k]['name'] = lines[k]["motion:name"];
              linesselected.push(lines[k]);
          }
        }

        spdata = [];
        voters.forEach(function(d) {
          spdata.push({"x":d["wpca:d1"],"y":d["wpca:d2"],"r":d["r"],"color":d["color"],"name":d["name"],"opacity":d["opacity"], "party": d["party"], "country": d["country"]})
        });
        var scatterplotwithlineplot = [{
          "data": spdata,
          "margin": {top: 10, right: 10, bottom: 30, left: 30},
          "axes": {"labels":{"x":"DIM 1", "y":"DIM 2"}},
          "minmax":{"x":{'min':-1,'max':1},"y":{'min':-1,'max':1},"r":{'min':0,'max':1},"rrange":{'min':0,'max':100}},
          "size":{"width":500,"height":500},
          "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>" + d["party"] + "<br>" + d["country"];
        });

        /* 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>

chart_data.py

# adds info to wpca results

import csv
import json
import slugify

g2c = {
    'Europe of Freedom and Direct Democracy Group':"#47cbd7",
    'Group of the Alliance of Liberals and Democrats for Europe':'gold',
    'Non-attached Members':'gray',
    'European Conservatives and Reformists Group':'darkblue',
    'Group of the Progressive Alliance of Socialists and Democrats in the European Parliament':'red',
    'Group of the Greens/European Free Alliance':'darkgreen',
    'Europe of Nations and Freedom Group':'#030E40',
    'Confederal Group of the European United Left - Nordic Green Left':'darkred',
    "Group of the European People's Party (Christian Democrats)":'blue'
}


meps = []
data = {}
files = ["meps_current","meps_ins","meps_outs"]
for f in files:
    with open("../" + f + ".json") as fin:
        meps = meps + json.load(fin)

ms = {}
for mep in meps:
    ms[mep['id']] = mep

with open("chart_data.csv","w") as fout:
    csvw = csv.writer(fout)
    csvw.writerow(["voter_id","wpca:d1","wpca:d2","name", "party", "political_group", "country", "country_code", "color"])
    with open("proj_unit.csv") as fin:
        csvd = csv.DictReader(fin)
        for row in csvd:
            m = ms[row["V1"]]
            r = [row["V1"],row["V2"],row["V3"],m['name'],m['party'],m['political_group'],m['country'],m['country_code'],g2c[m['political_group']]]
            csvw.writerow(r)

d3.scatterplot.js

/* requires D3 + https://github.com/Caged/d3-tip */
d3.scatterplotwithlineplot = function() {
  unit_circle = true;
  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),
          unit_circle_val = (typeof(unit_circle) === "function" ? unit_circle(d) : unit_circle);
      
      // 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)];
//      }
      
      //ellipse ~ unit_circle
      if (unit_circle_val) {
          element.selectAll(".ellipse")
              .data([0])
            .enter()
              .append("ellipse")
                .attr("cx", xScale(0))
                .attr("cy", yScale(0))
                .attr("rx", Math.abs(xScale(1)-xScale(0)))
                .attr("ry", Math.abs(yScale(1)-yScale(0)))
                .attr("fill-opacity",0)
                .attr("stroke","red")
                .style("stroke-dasharray", ("10,3"));
      }
      
      //lines
//      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';
//		   })
//		    //putting it here and not in css, because it is used for generating png:
//		 .attr("stroke","gray")
//		 .attr("stroke-width","1")
//		 .attr("opacity", 0.15)
//		 
//         .on('mouseover', tip.show)
//         .on('mouseout', tip.hide);
      
      //points
      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) {
		        if ((typeof(d['r']) != 'undefined') && (d['r'] != ''))
		            return rScale(d.r);
		   		else return rScale(0.05);
		   })
		   .attr("class", function(d) {
		   		if ((typeof(d['class']) != 'undefined') && (d['class'] != ''))
		   		     return d['class'];
		   		else return 'circle';
		   })
		   .attr("fill",function(d) {
		       if ((typeof(d['color']) != 'undefined') && (d['color'] != ''))
    		     return d.color;
    		   else
    		     return "#eee";
    	   })
		   .attr("stroke",function(d) {
		       if ((typeof(d['color']) != 'undefined') && (d['color'] != ''))
    		     return d.color;
    		   else
    		     return "#bbb";
    	   })
    	   .attr("stroke-width",function(d) {
		        if ((typeof(d['r']) != 'undefined') && (d['r'] != ''))
		            return Math.max(d.r*5,1);
		   		else return 1;
    	   })
		   .attr("fill-opacity",function(d) {
		       if ((typeof(d['opacity']) != 'undefined') && (d['opacity'] != ''))
    		     return d.opacity;
    		   else
    		     return 0.5;
    	   })
		   .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']);
			 
	    
	  // putting it here and not in css, because it is used for generating png: 
	  element.selectAll(".domain")
	        	.attr("fill","none")
				.attr("fill-opacity",0)
				.attr("stroke","black")
				.attr("stroke-width",1);
      element.selectAll(".tick")
                .attr("fill-opacity",0)
                .attr("stroke","#000")
                .attr("stroke-width",1);
      element.selectAll("text")
                .attr("font-family","sans-serif")
                .attr("font-size",11)
      element.selectAll(".label")
                .attr("font-size",15)
                .attr("font-weight","bold")
                //convert dy("0.71em") to dy("10"), inkscape feature https://www.ruby-forum.com/topic/5505193 :
      element.selectAll("text")
                .attr("dy",function(d) {
                    if (d3.select(this).attr("dy")) {
                        em = parseFloat(d3.select(this).attr("dy").replace("em",""));
                        if (d3.select(this).attr("font-size"))
                            px = parseFloat(d3.select(this).attr("font-size"));
                        else
                            px = 11;
                        return em*px + "px";
                    } else {
                        return 0;
                    }
                })
    });
  }
  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;
  };
  scatterplotwithlineplot.unit_circle = function(value) {
    if (!arguments.length) return value;
    unit_circle = 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.scatterplotwithlineplot.js

/* requires D3 + https://github.com/Caged/d3-tip */
d3.scatterplotwithlineplot = function() {
  unit_circle = true;
  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),
          unit_circle_val = (typeof(unit_circle) === "function" ? unit_circle(d) : unit_circle);
      
      // 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)];
      }
      
      //ellipse ~ unit_circle
      if (unit_circle_val) {
          element.selectAll(".ellipse")
              .data([0])
            .enter()
              .append("ellipse")
                .attr("cx", xScale(0))
                .attr("cy", yScale(0))
                .attr("rx", Math.abs(xScale(1)-xScale(0)))
                .attr("ry", Math.abs(yScale(1)-yScale(0)))
                .attr("fill-opacity",0)
                .attr("stroke","red")
                .style("stroke-dasharray", ("10,3"));
      }
      
      //lines
      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';
		   })
		    //putting it here and not in css, because it is used for generating png:
		 .attr("stroke","gray")
		 .attr("stroke-width","1")
		 .attr("opacity", 0.15)
		 
         .on('mouseover', tip.show)
         .on('mouseout', tip.hide);
      
      //points
      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) {
		        if ((typeof(d['r']) != 'undefined') && (d['r'] != ''))
		            return rScale(d.r);
		   		else return rScale(0.075);
		   })
		   .attr("class", function(d) {
		   		if ((typeof(d['class']) != 'undefined') && (d['class'] != ''))
		   		     return d['class'];
		   		else return 'circle';
		   })
		   .attr("fill",function(d) {
		       if ((typeof(d['color']) != 'undefined') && (d['color'] != ''))
    		     return d.color;
    		   else
    		     return "#eee";
    	   })
		   .attr("stroke",function(d) {
		       if ((typeof(d['color']) != 'undefined') && (d['color'] != ''))
    		     return d.color;
    		   else
    		     return "#bbb";
    	   })
    	   .attr("stroke-width",function(d) {
		        if ((typeof(d['r']) != 'undefined') && (d['r'] != ''))
		            return Math.max(d.r*5,1);
		   		else return 1;
    	   })
		   .attr("fill-opacity",function(d) {
		       if ((typeof(d['opacity']) != 'undefined') && (d['opacity'] != ''))
    		     return d.opacity;
    		   else
    		     return 1;
    	   })
		   .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']);
			 
	    
	  // putting it here and not in css, because it is used for generating png: 
	  element.selectAll(".domain")
	        	.attr("fill","none")
				.attr("fill-opacity",0)
				.attr("stroke","black")
				.attr("stroke-width",1);
      element.selectAll(".tick")
                .attr("fill-opacity",0)
                .attr("stroke","#000")
                .attr("stroke-width",1);
      element.selectAll("text")
                .attr("font-family","sans-serif")
                .attr("font-size",11)
      element.selectAll(".label")
                .attr("font-size",15)
                .attr("font-weight","bold")
                //convert dy("0.71em") to dy("10"), inkscape feature https://www.ruby-forum.com/topic/5505193 :
      element.selectAll("text")
                .attr("dy",function(d) {
                    if (d3.select(this).attr("dy")) {
                        em = parseFloat(d3.select(this).attr("dy").replace("em",""));
                        if (d3.select(this).attr("font-size"))
                            px = parseFloat(d3.select(this).attr("font-size"));
                        else
                            px = 11;
                        return em*px + "px";
                    } else {
                        return 0;
                    }
                })
    });
  }
  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;
  };
  scatterplotwithlineplot.unit_circle = function(value) {
    if (!arguments.length) return value;
    unit_circle = 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
  };

}));

data.py

# prepare data for wpca

import json
import slugify
import csv

countries = []
with open("../member_states.csv") as fin:
    csvd = csv.DictReader(fin)
    for row in csvd:
        countries.append(row['code'])

meps = []
data = {}
files = ["meps_current","meps_ins","meps_outs"]
for f in files:
    with open("../" + f + ".json") as fin:
        meps = meps + json.load(fin)

name2id = {}
for mep in meps:
    name2id[slugify.slugify(mep['name'])] = mep['id']

g2g = {
    'Europe of Freedom and Direct Democracy Group':"group_icon_efd",
    'Group of the Alliance of Liberals and Democrats for Europe':'group_icon_aldeadle',
    'Non-attached Members':'group_icon_ni',
    'European Conservatives and Reformists Group':'group_icon_ecr',
    'Group of the Progressive Alliance of Socialists and Democrats in the European Parliament':'group_icon_sd',
    'Group of the Greens/European Free Alliance':'group_icon_greensefa',
    'Europe of Nations and Freedom Group':'group_icon_enf',
    'Confederal Group of the European United Left - Nordic Green Left':'group_icon_guengl',
    "Group of the European People's Party (Christian Democrats)":'group_icon_epp'
}

o2o = {
    'N': 'not voting',
    '-': 'no',
    '+': 'yes',
    '0': 'abstain',
    'A': 'absent',
    'M': 'absent'
}

def n4(n):
    if n > 999:
        return str(n)
    if n > 99:
        return "0" + str(n)
    if n > 9:
        return "00" + str(n)
    return "000" + str(n)

with open("x_source.csv","w") as fout:
    csvw = csv.writer(fout)
    csvw.writerow(["voter_id","vote_event_id","option"])
    for i in range(1,1309):
        print(i)
        path = "/home/michal/project/vaa2012-2/dev/ep2015/votes_votewatch/" + "vote_event_" + n4(i) + ".json"
        with open(path) as fin:
            votes = json.load(fin)
            for m in votes['all_votes']:
                option = o2o[m['euro_vot_valoare_sign']]
                voter_id = name2id[slugify.slugify(m['mep_name_name'])]
                vote_event_id = i
                csvw.writerow([voter_id,vote_event_id,option])