block by michalskop bc17b9b809e3501c47d1f398de9cbe8c

CZ 2013: Greens+Pirates+Change

Full Screen

index.html

<!DOCTYPE html>
<html>
  <head>
    <title>Zelení + Piráti + Změna 2013</title>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <meta name="viewport" content="initial-scale=1.0, user-scalable=no"/>
    <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootswatch/3.2.0/journal/bootstrap.min.css">
    <link rel="stylesheet" href="//cdn.leafletjs.com/leaflet-0.7.3/leaflet.css" />
    <script src="//cdn.leafletjs.com/leaflet-0.7.3/leaflet.js"></script>
    <script src="//d3js.org/d3.v3.min.js"></script>
    <script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
    <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/js/bootstrap.min.js"></script>
    <script src="d3.tip.js"></script>

    <style type="text/css">
      .leaflet-tile-pane {
		opacity: .4
      }
      .leaflet-container {
        background-color: #fff;
      }
      html, body{
		  width: 100%;
		  height: 100%;
		  /*margin: 0;
		  padding: 0;*/
		}
      #map {
		  width: 100%;
		  height: 90%;
	  }
	  circle {
        /*fill: #040;
        stroke: #040;*/
        cursor: pointer;
        fill-opacity: 0.8;
        stroke-opacity: 0.01;
    }
    circle:hover {
      stroke-opacity: 1;
    }

    .towns {
      width: 100000px;
      height: 100000px;
    }
	  /* this is because of Bootstrap - very important! */
		svg:not(:root) {
			overflow: visible;
		}

    /* D3 tips */
    .d3-tip {
      line-height: 1;
      /*font-weight: bold;*/
      padding: 12px;
      background: rgba(0, 0, 0, 0.8);
      color: #fff;
      border-radius: 2px;
    }
    .d3-tip small {
        font-size: 0.5em;
    }
    /* Style northward tooltips differently */
    .d3-tip.n:after {
      margin: -1px 0 0 0;
      top: 100%;
      left: 0;
    }
    .stronger {
      color: yellow;
      font-weight: bold;
    }
    /*overwrite bootstrap*/
        .navbar {
          margin-bottom: 0;
        }
    </style>
  </head>
  <body>
    <div class="navbar navbar-default" role="navigation">
      <div class="container">
        <div class="navbar-header">
          <a class="navbar-brand">Zelení + Piráti + Změna 2013</a>
        </div>
      </div>
    </div>
    <div class="btn-group" role="group" aria-label="kraje">
        <a class="btn btn-primary regions" href="#" role="button" value="Jihočeský kraj">Jihočeský</a>
        <a class="btn btn-primary regions" href="#" role="button" value="Jihomoravský kraj">Jihomoravský</a>
        <a class="btn btn-primary regions" href="#" role="button" value="Karlovarský kraj">Karlovarský</a>
        <a class="btn btn-primary regions" href="#" role="button" value="Královéhradecký kraj">Královéhradecký</a>
        <a class="btn btn-primary regions" href="#" role="button" value="Liberecký kraj">Liberecký</a>
        <a class="btn btn-primary regions" href="#" role="button" value="Moravskoslezský kraj">Moravskoslezský</a>
        <a class="btn btn-primary regions" href="#" role="button" value="Olomoucký kraj">Olomoucký</a>
        <a class="btn btn-primary regions" href="#" role="button" value="Pardubický kraj">Pardubický</a>
        <a class="btn btn-primary regions" href="#" role="button" value="Plzeňský kraj">Plzeňský</a>
        <a class="btn btn-primary regions" href="#" role="button" value="Středočeský kraj">Středočeský</a>
        <a class="btn btn-primary regions" href="#" role="button" value="Ústecký kraj">Ústecký</a>
        <a class="btn btn-primary regions" href="#" role="button" value="Kraj Vysočina">Vysočina</a>
        <a class="btn btn-primary regions" href="#" role="button" value="Zlínský kraj">Zlínský</a>
    </div>

    <div style="position:fixed;top:125px;z-index:1000;">
		<div class="alert alert-info" style="float:left;" id="legend">
		<h3>Zelení+Piráti+Změna</h3>
		    <svg height="20" width="20"><circle cx="10" cy="10" r="10" fill="blue"></svg> 10 %<br/>
		    <svg height="20" width="20"><circle cx="10" cy="10" r="10" fill="green"></svg> 6.4 %<br/>
		    <svg height="20" width="20"><circle cx="10" cy="10" r="10" fill="yellow"></svg> 5 %<br/>
		    <svg height="20" width="20"><circle cx="10" cy="10" r="10" fill="red"></svg> 0 %<br/>
		</div>
    </div>
    <div id="map"></div>
<script type="text/javascript">
// Create the map
  var map = L.map('map', {'zoomControl':false}).setView([50,15], 8);
  // add an OpenStreetMap tile layer
  // also see //wiki.openstreetmap.org/wiki/Tiles
  L.tileLayer('//{s}.www.toolserver.org/tiles/bw-mapnik/{z}/{x}/{y}.png', {
    attribution: 'CC-BY Michal Škop | &copy; <a href="//osm.org/copyright">OpenStreetMap</a> contributors | <a href="//ovolby.cz">ovolby.cz</a>'
  }).addTo(map);
  L.control.zoom({"position":"topright"}).addTo(map);

  //svg
  var svg = d3.select(map.getPanes().overlayPane).append("svg").attr("class", "towns");

  //scale
  var radiusScale = d3.scale.sqrt().domain([0, 586509]).range([0, 75]);
  var color = colorscale([0,0.05,0.0641,0.1,1],["red", "yellow", "green", "blue", "darkblue"]);

  /* Initialize tooltip */
  var tip = changetooltip();

  /* Invoke the tip in the context of your visualization */
  svg.call(tip);

  // Add tooltip div
  var div = d3.select("body").append("div")
    .attr("class", "tooltip")
    .style("opacity", 1e-6);
  d3.csv('cz_towns.csv', function(data) {
   d3.csv('sz_pirati_zmena_2013.csv',function(results_arr) {
       results = {};
       for (i=0;i<results_arr.length;i++) {
           results[results_arr[i]['id']] = results_arr[i];
       }
    //mapping
    nodes = data
      .map(function(d) {
        mpoint = projectPoint(d.lat,d.long);
        ret = {
          x: mpoint.x,
          y: mpoint.y,
          name: d.name,
          value: d  //including whole d
        };
        if (typeof(results[d.id]) != 'undefined') {
            ret['results'] = results[d.id];
            ret['r'] = radiusScale(results[d.id]["total"]);
        }
        return ret;
     });
    //adding circles
    var circle = svg.selectAll("svg")
      .data(nodes)
      .enter().append("svg:svg")
        .append("svg:circle")
        .attr("class","town")
        .attr("id", function (d) {return "id_" + d.value.id})
        .attr("cx", function (d) {return d.x})
        .attr("cy", function (d) {return d.y})
	    .attr("r", function (d) {return d.r})
	    .attr("fill", function (d) {
	      if (typeof(d.results) != 'undefined') {
	        return color(parseFloat(d.results['sz_pirati_zmena'])/d.results['total']);
	      } else {
	        return '#ccf';
	      }
	    })
        .on("mouseover", tip.show)
	    .on("mouseout", tip.hide);

	//on zoom or map movement, //leafletjs.com/reference.html#events
    map.on("viewreset", changeit);
    map.on("moveend", changeit);
    //map.on("move", hidepoints);   //not needed
    map.on("zoomstart", hidepoints);
    $(".towns").show(100);
   });
  });

  //helper function matrix to values
  function matrixVal(s) {
      return s.split('(')[1].split(')')[0].split(',');
  }

  function hidepoints() {
    $(".towns").hide();
  }

  // adjust points after map change (zoom, move)
  function changeit() {
     //Chromium/Chrome does not support well changes, so:
     var s = $(".leaflet-map-pane").css("-webkit-transform");  //chromium, opera
     if (typeof(s) == 'undefined')
       var s = $(".leaflet-map-pane").css("transform");  //ff
     var sar = matrixVal(s);

  	 //if towns in svg: (not used if it is div)
  	 $(".towns").css('left',-1*parseFloat(sar[4]));
     $(".towns").css('top',-1*parseFloat(sar[5]));

     d3.selectAll(".town").each(function(d,i) {
       //set correct x,y
       mpoint = projectPoint(d.value.lat,d.value.long,d.value.id);
       $(this)
         .attr("r",
           function() {
            if (typeof(d.results) != 'undefined')
                return radiusScale(d.results["total"]) * Math.pow(map.getZoom(),4) / 4096;	//power of 3 to show the results better in small scale
            else
                return 0;
           }
          )
          /*.attr("stroke-width",
            Math.abs(radiusScale(d.value.population.p9)-radiusScale(d.value.population.p6)) * Math.pow(map.getZoom(),3) / 729	//power of 3 to show the results better in small scale
          )*/
          .attr("cx", mpoint.x)
          .attr("cy", mpoint.y);

          //Reposition the SVG to cover the features.
          //note: if towns is div, next lines are needed; if it is svg, it is different:
          $(this).attr('transform',"translate(" + sar[4] + "," + sar[5] + ")");
          /*$(this).parent().css('left',-1*parseFloat(sar[4]));
          $(this).parent().css('top',-1*parseFloat(sar[5]));*/


        });
    $(".towns").show(300);

  }

  // Use Leaflet to implement a D3 geometric transformation.
  function projectPoint(x, y, id) {
	  var point = map.latLngToLayerPoint(new L.LatLng(x, y));
	  if (!isNumeric(point.x)) alert(id);
	  return point;
  }

  function changetooltip() {
    tip = d3.tip().attr('class', 'd3-tip').html(function(d) {
      html = '<span class="stronger">' + d.name + "</span><br>";
      if (typeof(d.results) != 'undefined') {
            html += 'SZ+Piráti+Změna: '+Math.round(parseFloat(d.results.sz_pirati_zmena)/parseFloat(d.results.total)*10000)/100 + ' % (' + d.results.sz_pirati_zmena + ')<br>';
            html += 'Piráti: '+Math.round(parseFloat(d.results.pirati)/parseFloat(d.results.total)*10000)/100 + ' % (' + d.results.pirati + ')<br>';
            html += 'Zelení: '+Math.round(parseFloat(d.results.sz)/parseFloat(d.results.total)*10000)/100 + ' % (' + d.results.sz + ')<br>';
            html += 'Změna: '+Math.round(parseFloat(d.results.zmena)/parseFloat(d.results.total)*10000)/100 + ' % (' + d.results.zmena + ')';      }
      /*if (typeof(d.value.description) != 'undefined') {
        html += d.value.description;
      }*/
      /*else
        html += 'Ešte nie je rozhodnuté';*/

      return html;
    });
    return tip;
  }

function isNumeric(n) {
  return !isNaN(parseFloat(n)) && isFinite(n);
}
  //set color scale
  function colorscale(domain,range) {
    return d3.scale.linear()
            .domain(domain)
            .range(range);
  }

  //regions only
$(document).ready(function() {
    $(".regions").on('click',function() {
        var reg = $(this);
        d3.selectAll(".town").each(function(d,i) {
            if (d.results.region == reg.attr('value')) {
                $('#id_'+d.value.id).show();
            } else {
                $('#id_'+d.value.id).hide();
            }
        });
    });
});
</script>

    <script>
      (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
      (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
      m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
      })(window,document,'script','//www.google-analytics.com/analytics.js','ga');

      ga('create', 'UA-8592359-13', 'ocks.org');
      ga('send', 'pageview');

    </script>

  </body>
</html>

chart.html

<!DOCTYPE html>
<html lang="sk">
  <head>
    <meta charset="utf-8">
    <title>Zelení + Piráti + Změna 2013</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">

    <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootswatch/3.2.0/journal/bootstrap.min.css">
    <script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>

    <script src="//d3js.org/d3.v3.min.js"></script>
    <script src="./d3.scatterplot.js"></script>
    <script src="./d3.tip.js"></script>

    <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 {
		      fill-opacity: .5;
		      fill:#080;
		      stroke:#080;
		      stroke-opacity: 0.99;
		      stroke-width: 1;

		    }
			circle:hover {
			  fill-opacity: 1;
			}
			.label {
			  font-family: sans-serif;
			  font-size: 15px;
			}
            .stronger {
              color: yellow;
              font-weight: bold;
            }

			.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: .15;
            }
            .centerline {
		      stroke: #b00;
		      stroke-width: 3;
		      stroke-opacity: 1;
		    }
            .averageline {
		      stroke: #080;
		      stroke-width: 2;
		      stroke-opacity: 1;
		      stroke-dasharray: 10, 5;
		    }
            #chart {
              /*margin-top:100px;*/
            }
		</style>
  </head>
  <body>

          <h1>Zelení + Piráti + Změna 2013</h1>
          <div class="btn-group" role="group" aria-label="kraje">
    <a class="btn btn-primary regions" href="#" role="button" value="Jihočeský kraj">Jihočeský</a>
    <a class="btn btn-primary regions" href="#" role="button" value="Jihomoravský kraj">Jihomoravský</a>
    <a class="btn btn-primary regions" href="#" role="button" value="Karlovarský kraj">Karlovarský</a>
    <a class="btn btn-primary regions" href="#" role="button" value="Královéhradecký kraj">Královéhradecký</a>
    <a class="btn btn-primary regions" href="#" role="button" value="Liberecký kraj">Liberecký</a>
    <a class="btn btn-primary regions" href="#" role="button" value="Moravskoslezský kraj">Moravskoslezský</a>
    <a class="btn btn-primary regions" href="#" role="button" value="Olomoucký kraj">Olomoucký</a>
    <a class="btn btn-primary regions" href="#" role="button" value="Pardubický kraj">Pardubický</a>
    <a class="btn btn-primary regions" href="#" role="button" value="Plzeňský kraj">Plzeňský</a>
    <a class="btn btn-primary regions" href="#" role="button" value="Středočeský kraj">Středočeský</a>
    <a class="btn btn-primary regions" href="#" role="button" value="Ústecký kraj">Ústecký</a>
    <a class="btn btn-primary regions" href="#" role="button" value="Kraj Vysočina">Vysočina</a>
    <a class="btn btn-primary regions" href="#" role="button" value="Zlínský kraj">Zlínský</a>
</div>
    <div id="chart"></div>
    <p class="bg-info"><span class="glyphicon glyphicon-info-sign" aria-hidden="false"> </span> Každý bod je jedna obec. Velikost bodu reprezentuje počet voličů. Osa x je součet zisku Zelených, Pirátů a Změny (%). Osa y je jen náhodné číslo, aby graf byl přehlednější.
    <footer><small>CC-BY Michal Škop, <a href="//kohovolit.eu">KohoVolit.eu</a></small></footer>


<script type="text/javascript">
var radiusScale = d3.scale.sqrt().domain([0, 586509]).range([0, 75]);

d3.csv('cz_towns.csv', function(data) {
    d3.csv('sz_pirati_zmena_2013.csv',function(results_arr) {
         results = {};
         for (i=0;i<results_arr.length;i++) {
             results[results_arr[i]['id']] = results_arr[i];
         }
        points = [];
        data.forEach(function(d) {
            if (typeof(results[d.id]) != 'undefined') {
                d2 = results[d.id];
                points.push({
                    'r' : radiusScale(d2["total"]),
                    'y': Math.random(),
                    'x': parseFloat(parseFloat(d2.sz_pirati_zmena)/d2.total),
                    'name': d.name,
                    'class': 'town id_' + d2.id,
                    'region': d2.region,
                    'id': d.id
                });
            }
        });
        var scatterplot = [{
            "data": points,
            "margin": {top: 10, right: 10, bottom: 30, left: 30},
            "axes": {"labels":{"y":"", "x":"Zelení + Piráti + Změna 2013"}},
            "minmax":{
                "x":{'min':0,'max':0.2},
                "y":{'min':0,'max':1},
                "r":{'min':0,'max':Math.sqrt(586509)},
                "rrange":{'min':0,'max':500}
            },
            "size":{"width":1000,"height":200},
        }];

         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>" +
           'Zelení+Piráti+Změna: '+Math.round(d.x*10000)/100 + ' %';
        });

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

        var line = d3.svg.line()
            .x(
            function(d) {
            return d[0]; })
            .y(function(d) { return d[1]; });

        var width = scatterplot[0].size['width'] - scatterplot[0].margin.left - scatterplot[0].margin.right,
            height = scatterplot[0].size['height'] - scatterplot[0].margin.top - scatterplot[0].margin.bottom;

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

        centerline = Array([xScale(0.05),yScale(scatterplot[0].minmax['y']['min'])],[xScale(0.05),yScale(scatterplot[0].minmax['y']['max'])]);
        svg.append("path")
          .datum(centerline)
          .attr("class", "centerline")
          .attr("d", line)
          .attr("transform", "translate(" + scatterplot[0].margin.left + "," + scatterplot[0].margin.top + ")");

      averageline = Array([xScale(scatterplot[0].minmax['x']['min']),yScale(21.4)],[xScale(scatterplot[0].minmax['x']['max']),yScale(21.4)]);
        svg.append("path")
          .datum(averageline)
          .attr("class", "averageline")
          .attr("d", line)
          .attr("transform", "translate(" + scatterplot[0].margin.left + "," + scatterplot[0].margin.top + ")");
    })
})

//regions only
$(document).ready(function() {
  $(".regions").on('click',function() {
      var reg = $(this);
      d3.selectAll(".town").each(function(d,i) {
          if (d.region == reg.attr('value')) {
              $('.id_'+d.id).show();
          } else {
              $('.id_'+d.id).hide();
          }
      });
  });
});
</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(d.r);
		   })
		   .attr("class", function(d) {
		   		if (typeof(d['class'] != 'undefined')) return d['class'];
		   		else return 'circle';
		   })
           .attr("region", function(d) {
		   		if (typeof(d['region'] != 'undefined')) return d['region'];
		   		else return '';
		   })
		   .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.tip.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 {
    // 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() {
      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,
          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
  };

}));

script.py

import csv
import json

with open("cz2014towns.json") as fin:
    towns = json.load(fin)

with open("cz_towns.csv","w") as fout:
    header = ['id','name','lat','long']
    csvdw = csv.DictWriter(fout,fieldnames=header)
    csvdw.writeheader();
    for town in towns:
        row = {
            'id': town['id'],
            'name': town['name'],
            'lat': town['lat'],
            'long': town['lng']
        }
        csvdw.writerow(row)