D3
OG
Old school D3 from simpler times
All examples
By author
By category
About
ginseng666
Full window
Github gist
Polling Data Austria 2006-2015
<!DOCTYPE html> <head> <meta charset="utf-8"> <title>Polling Data Austria 2006-2015</title> <script src="https://d3js.org/d3.v3.min.js" charset="utf-8"></script> <link rel="stylesheet" type="text/css" href="style.css" /> </head> <body> <h1>Polling Data Austria, 2006-2015</h1> <div id="teaser"><p>Newspapers publish opinion polls regularly. While single results for the so called "Sonntagsfrage" ("Who would you vote for at the moment?") fluctuate often, a time series can show some general trends - like a decline in support for SPOE and OEVP, the regain in strength of the FPOE (after almost breaking apart), the rise and fall of the BZOE after the death of its leader Joerg Haider or the limited success of new parties. Adding the margin of error shows another thing: Many times it is not possible to identify the strongest party with certainty. Since the last election in 2013 three parties are competing for the first place.</p> The chart combines polling data published in Austrian newspapers from 2006 to 2015 with some important political events. Parties with no seats in parliament were excluded. Clicking on the chart allows to zoom in and out. The margin of error is calculated for each poll, based on its sample size (0.05 significance). If no size is provided, a sample of 500 people is assumed.</p></div> <div id="me"> <label>margin of error<input type="checkbox" name="me" value="me" id="margin_error" onClick="merr()"></input></label> </div> <div id="annotations"> <label>show/hide annotations<input type="checkbox" name="anno" value="anno" onClick="anno()" checked></input></label> </div> <div id="chart"></div> <div id="det"> <div id="bar"></div> <div id="details"> <div id="tab"> <table> <tr> <td>Date:</td><td id="date"></td> </tr> <tr> <td>Institute:</td><td id="institute"></td> </tr> <tr> <td>Source:</td><td id="source"></td> </tr> <tr> <td>Sample:</td><td id="sample"></td> </tr> </table> </div> </div> </div> <script> var margin= {top: 20, right: 20, bottom: 20, left: 25}; var width=1000 - margin.left - margin.right; var height=400 - margin.top - margin.bottom; var bwidth=300; var bheight=150; var barheight=0; var bpad=10; var btext=80; var datastore; var parties=[]; var exclude=4; //constant to exclude a certain number of fields and get only the party names var elections=[{"date":"01.10.2006","cap":"Federal Election 2006","color":"grey","pos":150},{"date":"08.08.2008","cap":"Faymann takes over SPOE","color":"#C83D44","pos":75},{"date":"28.09.2008","cap":"Federal Election 2008","color":"grey","pos":100},{"date":"29.09.2013","cap":"Federal Election 2013","color":"grey","pos":75},{"date":"11.10.2008","cap":"Joerg Haider dies","color":"#E68A00","pos":150},{"date":"25.09.2012","cap":"Team Stronach founded","color":"#CFCF53","pos":100},{"date":"27.10.2012","cap":"NEOS founded","color":"#B82593","pos":125},{"date":"01.09.2014","cap":"Mitterlehner takes over OEVP","color":"#191919","pos":125}]; //the annotations, containing the date of the event, the text and the y-position var skalax=d3.time.scale().rangeRound([margin.left, width - margin.right]); var skalay=d3.scale.linear().domain([0,100]).range([height-margin.top, margin.bottom]); var skalabars=d3.scale.linear().domain([0,100]).range([btext,bwidth]); var skalaw=d3.scale.linear().domain([0,100]).range([0,bwidth-btext]); var xaxis=d3.svg.axis().scale(skalax).orient("bottom").tickSize(0); var yaxis=d3.svg.axis().scale(skalay).orient("left").tickSize(0).ticks(5); var baraxis=d3.svg.axis().scale(skalabars).orient("bottom").tickValues([10,25,50,75]).tickSize(0); var date_compare; var k=0; var anno_flag=0; var op=1; var party_max=0; var format=d3.time.format("%d.%m.%Y"); var svg = d3.select("#chart").append("svg").attr("width", width).attr("height", height).on("click",zoom); var area = d3.svg.area(); var zoom=false; var dots=svg.selectAll(".dots"); var bars=d3.select("#bar").append("svg").attr("width", bwidth).attr("height", bheight); d3.csv("polls.csv", function(data) { parties=Object.keys(data[0]).slice(exclude,data[0].length); //get the party names barheight=(bheight-bpad-3)/(parties.length-1); //calculate the height of the bars for (i=0;i<data.length;i++) //parse the date; if two or more polls came out the same day, add one, two, three etc. hours to set them apart on the scale { data[i].date=format.parse(data[i].date); if (i>0) { if (k==0) { if (data[i].date.getTime() == data[i-1].date.getTime()) { date_compare=data[i].date.getTime(); k=1; data[i].date.setHours(data[i].date.getHours()+3*k); //set the date 3 hours*k apart } } else { if (data[i].date.getTime() == date_compare) { date_compare=data[i].date.getTime(); k=k+1; data[i].date.setHours(data[i].date.getHours()+3*k); } else { k=0; } } } } datastore=data; skalax.domain(d3.extent(datastore, function(d){return d.date;})); //set the domain for the time scale area.x(function(d){return skalax(d.date);}); for (k=0;k<parties.length-1;k++) { if (party_max<d3.max(datastore,function(d){return +d[parties[k]];})) party_max=d3.max(datastore,function(d){return +d[parties[k]];}); //get the highest value, needed for the zoom on the y-axis area.y0(function(d){return skalay(+d[parties[k]]);}) .y1(function(d){return skalay(+d[parties[k]]);}) .interpolate("cardinal") .defined(function(d){return d[parties[k]];}); //using defined to omit missing values when certain parties did not exist svg.append("path") .attr("d", area(datastore)) .attr("class",parties[k]) .attr("clip-path", "url(#blockchart)"); dots.data(datastore.filter(function(d){return d[parties[k]]>0;})) //adding invisible dots for the mouseover .enter().append("circle") .attr("cx",function(d){return skalax(d.date);}) .attr("cy",function(d){return skalay(d[parties[k]]);}) .attr("r",5).attr("id",function(d,i){return "id"+(i);}) .attr("class","dots") .on("mouseover",function(d){tool(d);}); bars.append("rect") .attr("x",skalabars(0)) .attr("y",barheight*k) .attr("width",0) .attr("height",barheight) .attr("class",parties[k]); bars.append("text") .attr("x",0) .attr("y",barheight*(k+1)) .attr("dy","-0.5em") .style("text-anchor","start") .text(parties[k].toUpperCase()); bars.append("text") .attr("x",skalabars(0)-3) .attr("y",barheight*(k+1)) .attr("dy","-0.5em") .attr("class","value"+k) .style("text-anchor","end") .text(); } for (i=0;i<elections.length;i++) //append the annotations { var g=svg.append("g").attr("class","elections"); g.append("line") .attr("x1",skalax(format.parse(elections[i].date))) .attr("x2",skalax(format.parse(elections[i].date))) .attr("y1",elections[i].pos) .attr("y2",height-margin.bottom) .attr("id","line"+i) .style("stroke",elections[i].color); g.append("text") .attr("x",skalax(format.parse(elections[i].date))-4) .attr("y",elections[i].pos-3) .attr("id","cap"+i) .style("text-anchor",function(){return i==elections.length-1 ? "middle" : "start";}) .text(elections[i].cap); g.append("title") .text(elections[i].date); } bars.append("g") .attr("class","axis") .attr("id","baraxis") .attr("transform","translate(0,"+(bheight-bpad)+")") .call(baraxis); svg.append("g") .attr("class","axis") .attr("id","xaxis") .attr("transform","translate(0,"+(height-margin.bottom)+")") .call(xaxis); svg.append("g") .attr("class","axis") .attr("id","yaxis") .attr("transform","translate("+margin.left+",0)") .call(yaxis); svg.append("clipPath") .attr("id", "blockchart") .append("rect") .attr("x", margin.left) .attr("y", margin.top) .attr("width", width-margin.left-margin.right) .attr("height", height-margin.bottom); }); function merr() //show the margin of error { var sample,error; if (document.getElementById("margin_error").checked) { me=1; var op=0.3; } else { me=0; var op=1; } for (k=0;k<parties.length-1;k++) { area.y0(function(d) { +d.sample==0 ? sample=500 : sample=+d.sample; //if no sample size is provided, set the sample size for the margin of error calculation error=1.96*(Math.sqrt(+d[parties[k]]*(100-(+d[parties[k]]))/sample)); return skalay(+d[parties[k]]+error*me); }) .y1(function(d){return skalay(+d[parties[k]]-error*me);}) .interpolate("cardinal") .defined(function(d){return d[parties[k]];}); svg.selectAll("."+parties[k]) .transition() .duration(750) .attr("d", area(datastore)) .style("opacity",op); } } function zoom() { zskalex=d3.time.scale() //use an inverted scale to get from mouse position to time position .domain([margin.left, width - margin.right]) .range(d3.extent(datastore, function(d){return d.date;})); document.getElementById("margin_error").checked = false; if ((d3.mouse(this)[0]-50)<margin.left) //limiting the zooming at the edges left and right { var xx1=d3.min(datastore,function(d){return d.date;}); } else { var xx1=new Date(Math.round(zskalex(d3.mouse(this)[0]-50))); } if ((d3.mouse(this)[0]+50)>(width-margin.right)) { var xx2=d3.max(datastore,function(d){return d.date;}); } else { var xx2=new Date(Math.round(zskalex(d3.mouse(this)[0]+50))); } if (!zoom) { skalax.domain([xx1,xx2]); skalay.domain([0,party_max]); zoom=true; } else { skalax.domain(d3.extent(datastore, function(d){return d.date;})); skalay.domain([0,100]); zoom=false; } svg.selectAll(".dots").remove(); //to avoid moving all circles for (k=0;k<parties.length-1;k++) { area.x(function(d){return skalax(d.date);}) .y0(function(d){return skalay(+d[parties[k]]);}) .y1(function(d){return skalay(+d[parties[k]]);}) .defined(function(d){return d[parties[k]];}); svg.selectAll("."+parties[k]) .transition() .duration(750) .attr("d", area(datastore)) .style("opacity",op) .attr("clip-path", "url(#blockchart)"); dots.data(datastore.filter(function(d){return skalax(d.date)>=margin.left && skalax(d.date)<=(width-margin.right) && d[parties[k]]>0;})) //adding new invisible circles after filtering by date - reduces the number of elements .enter() .append("circle") .attr("cx",function(d){return skalax(d.date);}) .attr("cy",function(d){return skalay(d[parties[k]]);}) .attr("r",7) .attr("id",function(d,i){return "id"+(i);}) .attr("class","dots") .on("mouseover",function(d){tool(d);}); } svg.select("#xaxis").transition().duration(750).call(xaxis); svg.select("#yaxis").transition().duration(750).call(yaxis); for (i=0;i<elections.length;i++) //add the annotations { svg.select("#line"+i).transition() .duration(750) .attr("x1",skalax(format.parse(elections[i].date))) .attr("x2",skalax(format.parse(elections[i].date))); svg.select("#cap"+i) .transition() .duration(750) .attr("x",skalax(format.parse(elections[i].date))-4); //trying to move them alltogether as a group, but somehow the translate did not produce the right position //attr("transform","translate("+skalax(format.parse(elections[i].date))+",0)"); //svg.select("#event"+i).transition().duration(750).attr("transform","translate(0,0)"); } } function anno() //annotations on/off { if (anno_flag==0) { svg.selectAll(".elections").style("visibility","hidden"); anno_flag=1; } else { svg.selectAll(".elections").style("visibility","visible"); anno_flag=0; } } function tool(d,coord) //the "tooltip" at the bottom { d3.select("#bars").append("line").attr("x1",btext).attr("x2",btext).attr("y1",20).attr("y2",40).style("stroke","steelblue"); var details=d3.select("#details"); if (d.sample==0) var tempsample="n.a."; else tempsample=+d.sample; details.select("#date").text(format(d.date)); details.select("#institute").text(d.institute); details.select("#source").text(d.source); details.select("#sample").text(tempsample); for (i=0;i<parties.length-1;i++) { bars.select("."+parties[i]).transition().duration(100).attr("width",function(){return skalaw(+d[parties[i]]);}); bars.selectAll(".value"+i).text(d[parties[i]]); } } </script> </body> </html>
Modified
http://d3js.org/d3.v3.min.js
to a secure url
https://d3js.org/d3.v3.min.js