Skip to content

Instantly share code, notes, and snippets.

@gabrielflorit
Last active May 15, 2019 17:52
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save gabrielflorit/25bfa64c04495b3f3203bb8e830957d2 to your computer and use it in GitHub Desktop.
Save gabrielflorit/25bfa64c04495b3f3203bb8e830957d2 to your computer and use it in GitHub Desktop.
Balance of power
license: mit
height: 720
border: no

Balance of power

.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}
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=
<!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 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()
.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