Balance of power
Last active
May 15, 2019 17:52
-
-
Save gabrielflorit/25bfa64c04495b3f3203bb8e830957d2 to your computer and use it in GitHub Desktop.
Balance of power
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
license: mit | |
height: 720 | |
border: no |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
.container{margin:1em;padding:0;position:relative}.container img{width:100%;opacity:1}span.label{display:inline-block;text-align:center;font-weight:700;width:2.75em}span.label.total{width:20em;text-align:left}input{width:100%}svg{display:block;border:1px solid red;width:50%;margin:0 auto}svg circle.ring{fill:none;stroke:grey;stroke-width:.01px}svg circle.dem{fill:#00f}svg circle.gop{fill:red}svg circle.ind{fill:green}svg circle.none{fill:grey;opacity:.25} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var rows=[d3.range(23),d3.range(26),d3.range(30),d3.range(32),d3.range(36),d3.range(38),d3.range(42),d3.range(46),d3.range(50),d3.range(54),d3.range(58)],totalSeats=d3.merge(rows).length,RADIUS=2.25,outerWidth=200,outerHeight=outerWidth/2+RADIUS,width=outerWidth-2*RADIUS,height=outerHeight-RADIUS,gap=width/2*.4,svg=d3.select("svg").attr("viewBox","0 0 "+outerWidth+" "+outerHeight).append("g").attr("transform","translate("+outerWidth/2+", "+height+")"),updateElement=function(t,e){return document.querySelector(t).textContent=e},drawData=function(){var t=+document.querySelector("input.dem").value,e=+document.querySelector("input.ind").value,n=+document.querySelector("input.gop").value;updateElement("span.dem",t),updateElement("span.ind",e),updateElement("span.gop",n);var a=totalSeats-(t+e+n);if(a>=0){updateElement("span.total",t+e+n+" out of "+totalSeats);var r=t,o=e,i=n,l=rows.map(function(a){return{total:a.length,demPortion:Math.ceil(t*a.length/totalSeats),indPortion:Math.ceil(e*a.length/totalSeats),gopPortion:Math.ceil(n*a.length/totalSeats)}}),d=l.map(function(t,e,n){var a,l,d,u,s=t.total,c=t.demPortion,g=t.indPortion,p=t.gopPortion;if(e===n.length-1)a=r,l=o,d=i,u=s-(a+l+d);else{u=s;var m=r>0?Math.min(r,c):0,h=u>0?Math.min(u,m):0;a=h,u-=a,r-=a;var S=o>0?Math.min(o,g):0,v=u>0?Math.min(u,S):0;l=v,u-=l,o-=l;var f=i>0?Math.min(i,p):0,w=u>0?Math.min(u,f):0;d=w,u-=d,i-=d}return{demSeats:a,indSeats:l,noneSeats:u,gopSeats:d,total:s}}),u=d3.merge(d.map(function(t,e){return d3.merge([d3.range(t.demSeats).map(function(){return"dem"}),d3.range(t.indSeats).map(function(){return"ind"}),d3.range(t.noneSeats).map(function(){return"none"}),d3.range(t.gopSeats).map(function(){return"gop"})]).reverse().map(function(t,n,a){return{party:t,row:e,seat:n,rowSeats:a.length}})})),s=d.length,c=d3.scalePoint().range([gap,width/2]).domain(d3.range(s+1));svg.selectAll("*").remove();var g=(svg.selectAll("circle.ring").data(d).enter().append("circle").attr("class","ring").attr("r",function(t,e){return c(e+1)}).attr("cx",0).attr("cy",0),svg.selectAll("circle.seat").data(u).enter().append("circle").attr("class",function(t){return t.party+" seat"}).attr("r",RADIUS).attr("cx",function(t){return c(t.row+1)}).attr("cy",0).attr("transform",function(t,e){return"rotate(-"+180/(t.rowSeats-1)*t.seat+")"}),svg.selectAll("circle.seat.dem").size()),p=svg.selectAll("circle.seat.ind").size(),m=svg.selectAll("circle.seat.gop").size();g===t&&p===e&&m===n||(alert("error! see the console. slider input did not match graph dots."),console.error(JSON.stringify({dem:{input:t,output:g},ind:{input:e,output:p},gop:{input:n,output:m}},null,2)))}else updateElement("span.total","Total exceeds available seats!")};d3.selectAll("input").on("input",function(t){return drawData()}),drawData(); | |
//# sourceMappingURL=data:application/json;charset=utf8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbInNjcmlwdC5qcyJdLCJuYW1lcyI6WyJjb25zdCIsInJvd3MiLCJkMyIsInJhbmdlIiwidG90YWxTZWF0cyIsIm1lcmdlIiwibGVuZ3RoIiwiUkFESVVTIiwib3V0ZXJXaWR0aCIsIm91dGVySGVpZ2h0Iiwid2lkdGgiLCJoZWlnaHQiLCJnYXAiLCJzdmciLCJzZWxlY3QiLCJhdHRyIiwiYXBwZW5kIiwidXBkYXRlRWxlbWVudCIsInNlbGVjdG9yIiwidmFsdWUiLCJkb2N1bWVudCIsInF1ZXJ5U2VsZWN0b3IiLCJ0ZXh0Q29udGVudCIsImRyYXdEYXRhIiwiZGVtIiwiaW5kIiwiZ29wIiwibm9uZSIsImxldCIsImRlbVNlYXRzTGVmdCIsImluZFNlYXRzTGVmdCIsImdvcFNlYXRzTGVmdCIsInJvd1Byb3BvcnRpb25zIiwibWFwIiwicm93IiwidG90YWwiLCJkZW1Qb3J0aW9uIiwiTWF0aCIsImNlaWwiLCJpbmRQb3J0aW9uIiwiZ29wUG9ydGlvbiIsInNlYXRSb3dzIiwiaW5kZXgiLCJhcnJheSIsImRlbVNlYXRzIiwiaW5kU2VhdHMiLCJnb3BTZWF0cyIsInJvd1NlYXRzTGVmdCIsImRlbVNlYXRzVG9Bc3NpZ24iLCJtaW4iLCJkZW1TZWF0c0F2YWlsYWJsZSIsImluZFNlYXRzVG9Bc3NpZ24iLCJpbmRTZWF0c0F2YWlsYWJsZSIsImdvcFNlYXRzVG9Bc3NpZ24iLCJnb3BTZWF0c0F2YWlsYWJsZSIsIm5vbmVTZWF0cyIsInNlYXRzIiwiaSIsInJldmVyc2UiLCJwYXJ0eSIsInNlYXQiLCJhIiwicm93U2VhdHMiLCJST1dTIiwicmluZ1dpZHRoIiwic2NhbGVQb2ludCIsImRvbWFpbiIsInNlbGVjdEFsbCIsInJlbW92ZSIsImRlbVNlbGVjdGlvblNpemUiLCJkYXRhIiwiZW50ZXIiLCJkIiwic2l6ZSIsImluZFNlbGVjdGlvblNpemUiLCJnb3BTZWxlY3Rpb25TaXplIiwiYWxlcnQiLCJjb25zb2xlIiwiZXJyb3IiLCJKU09OIiwic3RyaW5naWZ5Iiwi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R0FBQSxNQUFBLFdBQUEsS0FBQWUsRUFBQVYsU0FBQSxHQUFBVSxFQUFBWixLQUFBLE1BR1MvQyxJQUFJc0QsVUFBVSxtQkFBbUJNLFFBQXBEQyxFQUFtQjdELElBQUlzRCxVQUFVLG1CQUFtQk0sT0FDcERFLEVBQW1COUQsSUFBSXNELFVBQVUsbUJBQW1CTSxNQUdwREosS0FBcUI3QyxHQUEzQmtELElBQXNCakQsR0FDckJrRCxJQUFxQmpELElBR3JCa0QsTUFBTSxrRUFFTkMsUUFBUUMsTUFBTUMsS0FBS0MsV0FBbkJ4RCxLQUFReUQsTUFBTXpELEVBQUswRCxPQUFTYixHQUMzQjVDLEtBQU93RCxNQUFPeEQsRUFBS3lELE9BQVFSLEdBQzNCaEQsS0FBT3VELE1BQU92RCxFQUFLd0QsT0FBUVAsSUFDM0IsS0FBTyxTQU9UMUQsZUFBYyxhQUFjLGtDQU05QmYsSUFBR2lFLFVBQVUsU0FBU2dCLEdBQUcsUUFBUyxTQUFBWCxHQUFFLE1BQUdqRCxjQUF2Q0EiLCJmaWxlIjoic2NyaXB0LmpzIiwic291cmNlc0NvbnRlbnQiOlsiLy8gVGhpcyBhcnJheSBvZiBhcnJheXMgcmVwcmVzZW50cyBvdXIgZGVzaXJlZCBzZWF0aW5nIGFycmFuZ2VtZW50LlxuLy8gRWFjaCBuZXcgcm93IGhhcyBtb3JlIHNlYXRzIHRoYW4gdGhlIHByZXZpb3VzIG9uZS5cbmNvbnN0IHJvd3MgPSBbXG5cdGQzLnJhbmdlKDIzKSxcblx0ZDMucmFuZ2UoMjYpLFxuXHRkMy5yYW5nZSgzMCksXG5cdGQzLnJhbmdlKDMyKSxcblx0ZDMucmFuZ2UoMzYpLFxuXHRkMy5yYW5nZSgzOCksXG5cdGQzLnJhbmdlKDQyKSxcblx0ZDMucmFuZ2UoNDYpLFxuXHRkMy5yYW5nZSg1MCksXG5cdGQzLnJhbmdlKDU0KSxcblx0ZDMucmFuZ2UoNTgpLFxuXVxuXG4vLyBUaGUgdG90YWwgbnVtYmVyIG9mIHNlYXRzLlxuY29uc3QgdG90YWxTZWF0cyA9IGQzLm1lcmdlKHJvd3MpLmxlbmd0aFxuXG5jb25zdCBSQURJVVMgPSAyLjI1XG5cbmNvbnN0IG91dGVyV2lkdGggPSAyMDBcbmNvbnN0IG91dGVySGVpZ2h0ID0gKG91dGVyV2lkdGggLyAyKSArIFJBRElVU1xuXG5jb25zdCB3aWR0aCA9IG91dGVyV2lkdGggLSBSQURJVVMgKiAyXG5jb25zdCBoZWlnaHQgPSBvdXRlckhlaWdodCAtIFJBRElVU1xuXG5jb25zdCBnYXAgPSAod2lkdGggLyAyKSAqIDAuNFxuXG5jb25zdCBzdmcgPSBkMy5zZWxlY3QoJ3N2ZycpXG5cdFx0LmF0dHIoJ3ZpZXdCb3gnLCBgMCAwICR7b3V0ZXJXaWR0aH0gJHtvdXRlckhlaWdodH1gKVxuXHQuYXBwZW5kKCdnJylcblx0XHQuYXR0cigndHJhbnNmb3JtJyxcblx0XHRcdGB0cmFuc2xhdGUoJHtvdXRlcldpZHRoLzJ9LCAke2hlaWdodH0pYClcblxuY29uc3QgdXBkYXRlRWxlbWVudCA9IChzZWxlY3RvciwgdmFsdWUpID0+XG5cdGRvY3VtZW50LnF1ZXJ5U2VsZWN0b3Ioc2VsZWN0b3IpLnRleHRDb250ZW50ID0gdmFsdWVcblxuY29uc3QgZHJhd0RhdGEgPSAoKSA9PiB7XG5cblx0Y29uc3QgZGVtID0gK2RvY3VtZW50LnF1ZXJ5U2VsZWN0b3IoJ2lucHV0LmRlbScpLnZhbHVlXG5cdGNvbnN0IGluZCA9ICtkb2N1bWVudC5xdWVyeVNlbGVjdG9yKCdpbnB1dC5pbmQnKS52YWx1ZVxuXHRjb25zdCBnb3AgPSArZG9jdW1lbnQucXVlcnlTZWxlY3RvcignaW5wdXQuZ29wJykudmFsdWVcblx0dXBkYXRlRWxlbWVudCgnc3Bhbi5kZW0nLCBkZW0pXG5cdHVwZGF0ZUVsZW1lbnQoJ3NwYW4uaW5kJywgaW5kKVxuXHR1cGRhdGVFbGVtZW50KCdzcGFuLmdvcCcsIGdvcClcblx0XG5cdC8vIFRoaXMgaXMgZWFjaCBwYXJ0eSdzIHNlYXRzLlxuXHRjb25zdCBub25lID0gdG90YWxTZWF0cyAtIChkZW0gKyBpbmQgKyBnb3ApXG5cblx0aWYgKG5vbmUgPj0gMCkge1xuXG5cdFx0dXBkYXRlRWxlbWVudCgnc3Bhbi50b3RhbCcsIGAkeyhkZW0gKyBpbmQgKyBnb3ApfSBvdXQgb2YgJHt0b3RhbFNlYXRzfWApXG5cblx0XHQvLyBXZSdsbCB1c2UgdGhlc2UgdmFyaWFibGVzIHRvIHRyYWNrIHJlbWFpbmluZyBzZWF0cy5cblx0XHRsZXQgZGVtU2VhdHNMZWZ0ID0gZGVtXG5cdFx0bGV0IGluZFNlYXRzTGVmdCA9IGluZFxuXHRcdGxldCBnb3BTZWF0c0xlZnQgPSBnb3BcblxuXHRcdC8vIFNURVAgMTpcblx0XHQvLyBDYWxjdWxhdGUgZWFjaCByb3cncyByYXRpbyBvZiBwYXJ0eSBzZWF0cywgcm91bmRlZCB1cC5cblx0XHRjb25zdCByb3dQcm9wb3J0aW9ucyA9IHJvd3MubWFwKHJvdyA9PiAoe1xuXHRcdFx0XHR0b3RhbDogcm93Lmxlbmd0aCxcblx0XHRcdFx0ZGVtUG9ydGlvbjogTWF0aC5jZWlsKGRlbSAqIHJvdy5sZW5ndGggLyB0b3RhbFNlYXRzKSxcblx0XHRcdFx0aW5kUG9ydGlvbjogTWF0aC5jZWlsKGluZCAqIHJvdy5sZW5ndGggLyB0b3RhbFNlYXRzKSxcblx0XHRcdFx0Z29wUG9ydGlvbjogTWF0aC5jZWlsKGdvcCAqIHJvdy5sZW5ndGggLyB0b3RhbFNlYXRzKSxcblx0XHRcdH0pKVxuXG5cdFx0Ly8gU1RFUCAyOlxuXHRcdC8vIEFzc2lnbiBzZWF0cyB1bnRpbCB3ZSBydW4gb3V0IG9mIHRvdGFsIHBhcnR5IHNlYXRzLlxuXHRcdC8vIEZvciBlYWNoIHJvdyxcblx0XHRjb25zdCBzZWF0Um93cyA9IHJvd1Byb3BvcnRpb25zLm1hcCgocm93LCBpbmRleCwgYXJyYXkpID0+IHtcblxuXHRcdFx0Y29uc3QgeyB0b3RhbCwgZGVtUG9ydGlvbiwgaW5kUG9ydGlvbiwgZ29wUG9ydGlvbiB9ID0gcm93XG5cdFx0XHRsZXQgZGVtU2VhdHNcblx0XHRcdGxldCBpbmRTZWF0c1xuXHRcdFx0bGV0IGdvcFNlYXRzXG5cdFx0XHRsZXQgcm93U2VhdHNMZWZ0XG5cblx0XHRcdC8vIElmIHRoaXMgaXMgdGhlIGxhc3Qgcm93LCBzaW1wbHkgYXNzaWduIGFsbCBsZWZ0b3ZlciBzZWF0cy5cblx0XHRcdGlmIChpbmRleCA9PT0gYXJyYXkubGVuZ3RoIC0gMSkge1xuXG5cdFx0XHRcdGRlbVNlYXRzID0gZGVtU2VhdHNMZWZ0XG5cdFx0XHRcdGluZFNlYXRzID0gaW5kU2VhdHNMZWZ0XG5cdFx0XHRcdGdvcFNlYXRzID0gZ29wU2VhdHNMZWZ0XG5cdFx0XHRcdHJvd1NlYXRzTGVmdCA9IHRvdGFsIC0gKGRlbVNlYXRzICsgaW5kU2VhdHMgKyBnb3BTZWF0cylcblxuXHRcdFx0fSBlbHNlIHtcblxuXHRcdFx0XHQvLyBJZiB0aGlzIGlzIE5PVCB0aGUgbGFzdCByb3csIGxldCdzIHByb2NlZWQuXG5cblx0XHRcdFx0cm93U2VhdHNMZWZ0ID0gdG90YWxcblxuXHRcdFx0XHQvLyBXZSB3YW50IHRvIGFzc2lnbiBkZW0gc2VhdHMgdG8gdGhpcyByb3cuIFJlbWVtYmVyIHRoYXQgd2Vcblx0XHRcdFx0Ly8gaGF2ZSBgZGVtU2VhdHNMZWZ0YCwgYWxsIHRoZSBzZWF0cyB3ZSBoYXZlIGxlZnQuXG5cdFx0XHRcdC8vIFdlIHdhbnQgdG8gYXNzaWduIGFzIG1hbnkgb2YgdGhvc2UgYXMgcG9zc2libGUsIGdpdmVuIG91ciByZW1haW5pbmdcblx0XHRcdFx0Ly8gc2xvdHMuXG5cdFx0XHRcdC8vIFNvIGZpcnN0LCBJRiB0aGVyZSBhcmUgZGVtIHNlYXRzIGxlZnQsIGZpbmQgdGhlIG1pbiBvZiBkZW1TZWF0c0xlZnRcblx0XHRcdFx0Ly8gYW5kIGRlbVByb3BvcnRpb246XG5cdFx0XHRcdGNvbnN0IGRlbVNlYXRzVG9Bc3NpZ24gPSBkZW1TZWF0c0xlZnQgPiAwID9cblx0XHRcdFx0XHRNYXRoLm1pbihkZW1TZWF0c0xlZnQsIGRlbVBvcnRpb24pIDogMFxuXG5cdFx0XHRcdC8vIE5leHQsIGZpbmQgdGhlIGF2YWlsYWJsZSBzbG90cyBsZWZ0IG9uIHRoaXMgcm93LCBhbmQgc2VhdCBhcyBtYW55IGFzXG5cdFx0XHRcdC8vIHBvc3NpYmxlLCB3aGljaCBpcyB0aGUgbWluIG9mIHJvd1NlYXRzTGVmdCBhbmQgZGVtU2VhdHNUb0Fzc2lnbi5cblx0XHRcdFx0Y29uc3QgZGVtU2VhdHNBdmFpbGFibGUgPSByb3dTZWF0c0xlZnQgPiAwID9cblx0XHRcdFx0XHRNYXRoLm1pbihyb3dTZWF0c0xlZnQsIGRlbVNlYXRzVG9Bc3NpZ24pIDogMFxuXG5cdFx0XHRcdC8vIFRoaXMgd2lsbCBiZSB0aGUgYWN0dWFsIGRlbSBzZWF0cyB3ZSBhc3NpZ24gdG8gdGhpcyByb3cuXG5cdFx0XHRcdGRlbVNlYXRzID0gZGVtU2VhdHNBdmFpbGFibGVcblxuXHRcdFx0XHQvLyBGaW5hbGx5LCB1cGRhdGUgdGhpcyByb3cncyBzZWF0cyBsZWZ0LFxuXHRcdFx0XHRyb3dTZWF0c0xlZnQgLT0gZGVtU2VhdHNcblxuXHRcdFx0XHQvLyBhbmQgdXBkYXRlIGRlbVNlYXRzTGVmdC5cblx0XHRcdFx0ZGVtU2VhdHNMZWZ0IC09IGRlbVNlYXRzXG5cblx0XHRcdFx0Ly8gV2UnbGwgZG8gdGhlIHNhbWUgdGhpbmcgZm9yIGluZDpcblx0XHRcdFx0Y29uc3QgaW5kU2VhdHNUb0Fzc2lnbiA9IGluZFNlYXRzTGVmdCA+IDAgP1xuXHRcdFx0XHRcdE1hdGgubWluKGluZFNlYXRzTGVmdCwgaW5kUG9ydGlvbikgOiAwXG5cdFx0XHRcdGNvbnN0IGluZFNlYXRzQXZhaWxhYmxlID0gcm93U2VhdHNMZWZ0ID4gMCA/XG5cdFx0XHRcdFx0TWF0aC5taW4ocm93U2VhdHNMZWZ0LCBpbmRTZWF0c1RvQXNzaWduKSA6IDBcblx0XHRcdFx0aW5kU2VhdHMgPSBpbmRTZWF0c0F2YWlsYWJsZVxuXHRcdFx0XHRyb3dTZWF0c0xlZnQgLT0gaW5kU2VhdHNcblx0XHRcdFx0aW5kU2VhdHNMZWZ0IC09IGluZFNlYXRzXG5cblx0XHRcdFx0Ly8gYW5kIGdvcC5cblx0XHRcdFx0Y29uc3QgZ29wU2VhdHNUb0Fzc2lnbiA9IGdvcFNlYXRzTGVmdCA+IDAgP1xuXHRcdFx0XHRcdE1hdGgubWluKGdvcFNlYXRzTGVmdCwgZ29wUG9ydGlvbikgOiAwXG5cdFx0XHRcdGNvbnN0IGdvcFNlYXRzQXZhaWxhYmxlID0gcm93U2VhdHNMZWZ0ID4gMCA/XG5cdFx0XHRcdFx0TWF0aC5taW4ocm93U2VhdHNMZWZ0LCBnb3BTZWF0c1RvQXNzaWduKSA6IDBcblx0XHRcdFx0Z29wU2VhdHMgPSBnb3BTZWF0c0F2YWlsYWJsZVxuXHRcdFx0XHRyb3dTZWF0c0xlZnQgLT0gZ29wU2VhdHNcblx0XHRcdFx0Z29wU2VhdHNMZWZ0IC09IGdvcFNlYXRzXG5cblx0XHRcdH1cblxuXHRcdFx0cmV0dXJuIHtcblx0XHRcdFx0ZGVtU2VhdHMsXG5cdFx0XHRcdGluZFNlYXRzLFxuXHRcdFx0XHRub25lU2VhdHM6IHJvd1NlYXRzTGVmdCxcblx0XHRcdFx0Z29wU2VhdHMsXG5cdFx0XHRcdHRvdGFsLFxuXHRcdFx0fVxuXG5cdFx0fSlcblxuXHRcdC8vIENvbnZlcnQgYHNlYXRSb3dzYCB0byB0aGUgc3RydWN0dXJlIHdlIG5lZWQ6IGFuIGFycmF5IG9mIHNlYXRzLlxuXHRcdGNvbnN0IHNlYXRzID0gZDMubWVyZ2UoXG5cdFx0XHRzZWF0Um93cy5tYXAoKHJvdywgaSkgPT5cblx0XHRcdFx0ZDMubWVyZ2UoW1xuXHRcdFx0XHRcdGQzLnJhbmdlKHJvdy5kZW1TZWF0cykubWFwKCgpID0+ICdkZW0nKSxcblx0XHRcdFx0XHRkMy5yYW5nZShyb3cuaW5kU2VhdHMpLm1hcCgoKSA9PiAnaW5kJyksXG5cdFx0XHRcdFx0ZDMucmFuZ2Uocm93Lm5vbmVTZWF0cykubWFwKCgpID0+ICdub25lJyksXG5cdFx0XHRcdFx0ZDMucmFuZ2Uocm93LmdvcFNlYXRzKS5tYXAoKCkgPT4gJ2dvcCcpLFxuXHRcdFx0XHRdKVxuXHRcdFx0XHQucmV2ZXJzZSgpXG5cdFx0XHRcdC5tYXAoKHBhcnR5LCBzZWF0LCBhKSA9PiAoeyBwYXJ0eSwgcm93OiBpLCBzZWF0LCByb3dTZWF0czogYS5sZW5ndGggfSkpXG5cdFx0XHQpXG5cdFx0KVxuXG5cdFx0Y29uc3QgUk9XUyA9IHNlYXRSb3dzLmxlbmd0aFxuXG5cdFx0Y29uc3QgcmluZ1dpZHRoID0gZDMuc2NhbGVQb2ludCgpXG5cdFx0XHQucmFuZ2UoW2dhcCwgd2lkdGgvMl0pXG5cdFx0XHQuZG9tYWluKGQzLnJhbmdlKFJPV1MgKyAxKSlcblxuXHRcdHN2Zy5zZWxlY3RBbGwoJyonKS5yZW1vdmUoKVxuXHRcdFxuXHRcdGNvbnN0IHJpbmdzID0gc3ZnLnNlbGVjdEFsbCgnY2lyY2xlLnJpbmcnKVxuXHRcdFx0XHQuZGF0YShzZWF0Um93cylcblx0XHRcdC5lbnRlcigpLmFwcGVuZCgnY2lyY2xlJylcblx0XHRcdFx0LmF0dHIoJ2NsYXNzJywgJ3JpbmcnKVxuXHRcdFx0XHQuYXR0cigncicsIChkLCBpKSA9PiByaW5nV2lkdGgoaSArIDEpKVxuXHRcdFx0XHQuYXR0cignY3gnLCAwKVxuXHRcdFx0XHQuYXR0cignY3knLCAwKVxuXG5cdFx0Y29uc3QgY2lyY2xlU2VhdHMgPSBzdmcuc2VsZWN0QWxsKCdjaXJjbGUuc2VhdCcpXG5cdFx0XHRcdC5kYXRhKHNlYXRzKVxuXHRcdFx0LmVudGVyKCkuYXBwZW5kKCdjaXJjbGUnKVxuXHRcdFx0XHQuYXR0cignY2xhc3MnLCBkID0+IGAke2QucGFydHl9IHNlYXRgKVxuXHRcdFx0XHQuYXR0cigncicsIFJBRElVUylcblx0XHRcdFx0LmF0dHIoJ2N4JywgZCA9PiByaW5nV2lkdGgoZC5yb3cgKyAxKSlcblx0XHRcdFx0LmF0dHIoJ2N5JywgMClcblx0XHRcdFx0LmF0dHIoJ3RyYW5zZm9ybScsIChkLCBpKSA9PiBgcm90YXRlKC0kezE4MC8oZC5yb3dTZWF0cyAtIDEpICogZC5zZWF0fSlgKVxuXG5cdFx0Y29uc3QgZGVtU2VsZWN0aW9uU2l6ZSA9IHN2Zy5zZWxlY3RBbGwoJ2NpcmNsZS5zZWF0LmRlbScpLnNpemUoKVxuXHRcdGNvbnN0IGluZFNlbGVjdGlvblNpemUgPSBzdmcuc2VsZWN0QWxsKCdjaXJjbGUuc2VhdC5pbmQnKS5zaXplKClcblx0XHRjb25zdCBnb3BTZWxlY3Rpb25TaXplID0gc3ZnLnNlbGVjdEFsbCgnY2lyY2xlLnNlYXQuZ29wJykuc2l6ZSgpXG5cblx0XHRpZiAoIShkZW1TZWxlY3Rpb25TaXplID09PSBkZW0gJiZcblx0XHRcdGluZFNlbGVjdGlvblNpemUgPT09IGluZCAmJlxuXHRcdFx0Z29wU2VsZWN0aW9uU2l6ZSA9PT0gZ29wKSkge1xuXG5cdFx0XHRhbGVydCgnZXJyb3IhIHNlZSB0aGUgY29uc29sZS4gc2xpZGVyIGlucHV0IGRpZCBub3QgbWF0Y2ggZ3JhcGggZG90cy4nKVxuXG5cdFx0XHRjb25zb2xlLmVycm9yKEpTT04uc3RyaW5naWZ5KHtcblx0XHRcdFx0ZGVtOiB7IGlucHV0OiBkZW0sIG91dHB1dDogZGVtU2VsZWN0aW9uU2l6ZSB9LFxuXHRcdFx0XHRpbmQ6IHsgaW5wdXQ6IGluZCwgb3V0cHV0OiBpbmRTZWxlY3Rpb25TaXplIH0sXG5cdFx0XHRcdGdvcDogeyBpbnB1dDogZ29wLCBvdXRwdXQ6IGdvcFNlbGVjdGlvblNpemUgfSxcblx0XHRcdH0sIG51bGwsIDIpKVxuXG5cdFx0fVxuXG5cdH0gZWxzZSB7XG5cblx0XHR1cGRhdGVFbGVtZW50KCdzcGFuLnRvdGFsJywgYFRvdGFsIGV4Y2VlZHMgYXZhaWxhYmxlIHNlYXRzIWApXG5cblx0fVxuXG59XG5cbmQzLnNlbGVjdEFsbCgnaW5wdXQnKS5vbignaW5wdXQnLCBkID0+IGRyYXdEYXRhKCkpXG5kcmF3RGF0YSgpXG4iXX0= |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<title>Dot matrix</title> | |
<link href='dist.css' rel='stylesheet' /> | |
<body> | |
<p>US House balance of power</p> | |
<div class='container'> | |
<div class="intro"> | |
<p>Dem <span class="dem label">100</span><input class="dem" type="range" min="0" max="435" value="100"></span></p> | |
<p>Ind <span class="ind label">100</span><input class="ind" type="range" min="0" max="435" value="100"></span></p> | |
<p>GOP <span class="gop label">100</span><input class="gop" type="range" min="0" max="435" value="100"></span></p> | |
<p>Total: <span class="total label"></span></p> | |
</div> | |
<svg></svg> | |
</div> | |
<script src='https://cdn.jsdelivr.net/lodash/4.16.6/lodash.min.js'></script> | |
<script src='https://d3js.org/d3.v4.min.js'></script> | |
<script src='dist.js'></script> | |
</body> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// This array of arrays represents our desired seating arrangement. | |
// Each new row has more seats than the previous one. | |
const rows = [ | |
d3.range(23), | |
d3.range(26), | |
d3.range(30), | |
d3.range(32), | |
d3.range(36), | |
d3.range(38), | |
d3.range(42), | |
d3.range(46), | |
d3.range(50), | |
d3.range(54), | |
d3.range(58), | |
] | |
// The total number of seats. | |
const totalSeats = d3.merge(rows).length | |
const RADIUS = 2.25 | |
const outerWidth = 200 | |
const outerHeight = (outerWidth / 2) + RADIUS | |
const width = outerWidth - RADIUS * 2 | |
const height = outerHeight - RADIUS | |
const gap = (width / 2) * 0.4 | |
const svg = d3.select('svg') | |
.attr('viewBox', `0 0 ${outerWidth} ${outerHeight}`) | |
.append('g') | |
.attr('transform', | |
`translate(${outerWidth/2}, ${height})`) | |
const updateElement = (selector, value) => | |
document.querySelector(selector).textContent = value | |
const drawData = () => { | |
const dem = +document.querySelector('input.dem').value | |
const ind = +document.querySelector('input.ind').value | |
const gop = +document.querySelector('input.gop').value | |
updateElement('span.dem', dem) | |
updateElement('span.ind', ind) | |
updateElement('span.gop', gop) | |
// This is each party's seats. | |
const none = totalSeats - (dem + ind + gop) | |
if (none >= 0) { | |
updateElement('span.total', `${(dem + ind + gop)} out of ${totalSeats}`) | |
// We'll use these variables to track remaining seats. | |
let demSeatsLeft = dem | |
let indSeatsLeft = ind | |
let gopSeatsLeft = gop | |
// STEP 1: | |
// Calculate each row's ratio of party seats, rounded up. | |
const rowProportions = rows.map(row => ({ | |
total: row.length, | |
demPortion: Math.ceil(dem * row.length / totalSeats), | |
indPortion: Math.ceil(ind * row.length / totalSeats), | |
gopPortion: Math.ceil(gop * row.length / totalSeats), | |
})) | |
// STEP 2: | |
// Assign seats until we run out of total party seats. | |
// For each row, | |
const seatRows = rowProportions.map((row, index, array) => { | |
const { total, demPortion, indPortion, gopPortion } = row | |
let demSeats | |
let indSeats | |
let gopSeats | |
let rowSeatsLeft | |
// If this is the last row, simply assign all leftover seats. | |
if (index === array.length - 1) { | |
demSeats = demSeatsLeft | |
indSeats = indSeatsLeft | |
gopSeats = gopSeatsLeft | |
rowSeatsLeft = total - (demSeats + indSeats + gopSeats) | |
} else { | |
// If this is NOT the last row, let's proceed. | |
rowSeatsLeft = total | |
// We want to assign dem seats to this row. Remember that we | |
// have `demSeatsLeft`, all the seats we have left. | |
// We want to assign as many of those as possible, given our remaining | |
// slots. | |
// So first, IF there are dem seats left, find the min of demSeatsLeft | |
// and demProportion: | |
const demSeatsToAssign = demSeatsLeft > 0 ? | |
Math.min(demSeatsLeft, demPortion) : 0 | |
// Next, find the available slots left on this row, and seat as many as | |
// possible, which is the min of rowSeatsLeft and demSeatsToAssign. | |
const demSeatsAvailable = rowSeatsLeft > 0 ? | |
Math.min(rowSeatsLeft, demSeatsToAssign) : 0 | |
// This will be the actual dem seats we assign to this row. | |
demSeats = demSeatsAvailable | |
// Finally, update this row's seats left, | |
rowSeatsLeft -= demSeats | |
// and update demSeatsLeft. | |
demSeatsLeft -= demSeats | |
// We'll do the same thing for ind: | |
const indSeatsToAssign = indSeatsLeft > 0 ? | |
Math.min(indSeatsLeft, indPortion) : 0 | |
const indSeatsAvailable = rowSeatsLeft > 0 ? | |
Math.min(rowSeatsLeft, indSeatsToAssign) : 0 | |
indSeats = indSeatsAvailable | |
rowSeatsLeft -= indSeats | |
indSeatsLeft -= indSeats | |
// and gop. | |
const gopSeatsToAssign = gopSeatsLeft > 0 ? | |
Math.min(gopSeatsLeft, gopPortion) : 0 | |
const gopSeatsAvailable = rowSeatsLeft > 0 ? | |
Math.min(rowSeatsLeft, gopSeatsToAssign) : 0 | |
gopSeats = gopSeatsAvailable | |
rowSeatsLeft -= gopSeats | |
gopSeatsLeft -= gopSeats | |
} | |
return { | |
demSeats, | |
indSeats, | |
noneSeats: rowSeatsLeft, | |
gopSeats, | |
total, | |
} | |
}) | |
// Convert `seatRows` to the structure we need: an array of seats. | |
const seats = d3.merge( | |
seatRows.map((row, i) => | |
d3.merge([ | |
d3.range(row.demSeats).map(() => 'dem'), | |
d3.range(row.indSeats).map(() => 'ind'), | |
d3.range(row.noneSeats).map(() => 'none'), | |
d3.range(row.gopSeats).map(() => 'gop'), | |
]) | |
.reverse() | |
.map((party, seat, a) => ({ party, row: i, seat, rowSeats: a.length })) | |
) | |
) | |
const ROWS = seatRows.length | |
const ringWidth = d3.scalePoint() | |
.range([gap, width/2]) | |
.domain(d3.range(ROWS + 1)) | |
svg.selectAll('*').remove() | |
const rings = svg.selectAll('circle.ring') | |
.data(seatRows) | |
.enter().append('circle') | |
.attr('class', 'ring') | |
.attr('r', (d, i) => ringWidth(i + 1)) | |
.attr('cx', 0) | |
.attr('cy', 0) | |
const circleSeats = svg.selectAll('circle.seat') | |
.data(seats) | |
.enter().append('circle') | |
.attr('class', d => `${d.party} seat`) | |
.attr('r', RADIUS) | |
.attr('cx', d => ringWidth(d.row + 1)) | |
.attr('cy', 0) | |
.attr('transform', (d, i) => `rotate(-${180/(d.rowSeats - 1) * d.seat})`) | |
const demSelectionSize = svg.selectAll('circle.seat.dem').size() | |
const indSelectionSize = svg.selectAll('circle.seat.ind').size() | |
const gopSelectionSize = svg.selectAll('circle.seat.gop').size() | |
if (!(demSelectionSize === dem && | |
indSelectionSize === ind && | |
gopSelectionSize === gop)) { | |
alert('error! see the console. slider input did not match graph dots.') | |
console.error(JSON.stringify({ | |
dem: { input: dem, output: demSelectionSize }, | |
ind: { input: ind, output: indSelectionSize }, | |
gop: { input: gop, output: gopSelectionSize }, | |
}, null, 2)) | |
} | |
} else { | |
updateElement('span.total', `Total exceeds available seats!`) | |
} | |
} | |
d3.selectAll('input').on('input', d => drawData()) | |
drawData() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
.container | |
margin 1em | |
padding 0 | |
position relative | |
img | |
width 100% | |
opacity 1 | |
span.label | |
display inline-block | |
text-align center | |
font-weight bold | |
width 2.75em | |
&.total | |
width 20em | |
text-align left | |
input | |
width 100% | |
svg | |
display block | |
border solid red 1px | |
width 50% | |
margin 0 auto | |
circle | |
&.ring | |
fill none | |
stroke grey | |
stroke-width 0.01px | |
&.dem | |
fill blue | |
&.gop | |
fill red | |
&.ind | |
fill green | |
&.none | |
fill grey | |
opacity 0.25 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment