index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="//d3js.org/d3.v3.min.js"></script>
<link href="//netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.css" rel="stylesheet">
<link href="//maxcdn.bootstrapcdn.com/bootswatch/3.2.0/united/bootstrap.min.css" rel="stylesheet">
<style>
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
path, line {
stroke:#bbb;
stroke-width:1
}
text.shadow {
stroke: gray;
stroke-width: 1px;
opacity: 0.9;
}
.half {
fill: #888;
opacity:0.5;
}
</style>
</head>
<body>
<div class="navbar navbar-default">
<div class="container">
<div class="navbar-header">
<span class="navbar-brand">Hemicycle - for parliaments/councils of any size, with draggable majority arc</span>
</div>
</div>
</div>
<div id="chart"></div>
<div class="alert alert-info">
The <strong>algorithm.py</strong> calculates optimal number of representatives in each row for several numbers of rows (+ size of icons and gap between the rows). These numbers are used as parameters for the chart.
<br/><em>It may be slow for big parliaments, but it is needed just once for any number (e.g., 200 representatives took about 1 hour, due to the grid search - further optimization possible, my trial using steepest descent algorithm did not converge many times).</em>
</div>
<div class="col-lg-4">
<div class="bs-component">
<div class="list-group">
<a href="#" class="list-group-item active">Legend</a>
<a href="#" class="list-group-item">
<div id="legend"></div>
</a>
</div>
</div>
<div class="alert alert-info">
The legend is also created as a svg picture.
</div>
</div>
<script>
var margin = {top: 0, right: 0, bottom: 0, left: 0},
width = 600 - margin.left - margin.right,
height = 300 - margin.top - margin.bottom;
var svg = d3.select("#chart").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var groups = [
{'name':'KSČM','n':33,'color':'red'},
{'name':'Úsvit','n':14,'color':'pink'},
{'name':'ČSSD','n':50,'color':'orange'},
{'name':'KDU-ČSL','n':14,'color':'yellow'},
{'name':'ANO','n':47,'color':'aqua'},
{'name':'TOP 09','n':26,'color':'purple'},
{'name':'ODS','n':16,'color':'blue'}
];
var h = {
'n': [8,11,15,19,22,26,29,33,37],
'g': 1.20,
'w': 0.39,
}
rmax = 1 + h['n'].length *h['g']*h['w'];
var
xScale = d3.scale.linear()
.domain([-1*rmax, rmax])
.range([0, width]),
yScale = d3.scale.linear()
.domain([0, rmax])
.range([height,0])
x0Scale = d3.scale.linear()
.domain([0, 2*rmax])
.range([0, width]);
data = [];
s = [];
for (i in h['n']) {
s.push((Math.PI/h['w'] + Math.PI*i*h['g']-h['n'][i])/(h['n'][i] - 1));
ninrow = h['n'][i];
radwidth = Math.PI/(h['n'][i]+(h['n'][i]-1)*s[i]);
radspace = radwidth*s[i];
r = 1 + i*h['g']*h['w'];
for (j=0;j<ninrow;j++) {
x = Math.cos(radwidth*(0.5+j)+j*radspace)*r;
y = Math.sin(radwidth*(0.5+j)+j*radspace)*r;
rot = -1*(radwidth*(0.5+j)+j*radspace)/Math.PI*180+90;
data.push({'x':x,'y':y,'rot':rot});
}
}
data.sort(function(x,y) {
if (x['rot'] < y['rot']) return -1;
if (x['rot'] > y['rot']) return 1;
return 0
});
i = 0;
for (gkey in groups) {
group = groups[gkey];
for (j=0;j<group['n'];j++) {
data[i]['color'] = group['color'];
data[i]['name'] = group['name'];
i++;
}
}
var angle = [{'startangle':0,'endangle':Math.PI/2}];
var arci = d3.svg.arc()
.startAngle(function(d){return d.startangle})
.endAngle(function(d){return d.endangle})
.outerRadius(x0Scale(rmax))
.innerRadius(0);
var position = [xScale(0),yScale(0)];
var arc = svg.selectAll('.half')
.data(angle)
.enter()
.append("path")
.attr("d",arci)
.attr("transform", "translate(" + position + ")")
.attr("class","half");
var drag = d3.behavior.drag()
.on("drag", function(d,i) {
alpha1 = Math.atan((d3.event.x - xScale(0))/(-d3.event.y + yScale(0)));
x2 = d3.event.dx + d3.event.x;
y2 = d3.event.dy + d3.event.y;
alpha2 = Math.atan((x2 - xScale(0))/(-y2 + yScale(0)));
alpha = alpha2-alpha1;
angle[0]['startangle'] += alpha;
angle[0]['endangle'] += alpha;
arc.attr("d",arci);
});
arc.call(drag);
var icons = svg.selectAll(".icon")
.data(data)
.enter().append("text")
.attr('font-family', 'FontAwesome')
.attr('font-size',x0Scale(h['w']*1.15))
.attr('fill', function(d) {return d.color;})
.attr('text-anchor',"middle")
.attr('class', 'shadow')
.attr('x',function(d) {return xScale(d.x);})
.attr('y',function(d) {return yScale(d.y);})
.attr("transform",function(d) {return "rotate("+d.rot+","+xScale(d.x)+","+yScale(d.y)+")"})
.text('\uf007');
svg.append("text")
.attr('font-family', 'sans-serif')
.attr('font-size',x0Scale(h['w']*1))
.attr('font-weight','bold')
.attr('text-anchor',"middle")
.attr('fill', '#444')
.attr('x',xScale(0))
.attr('y',yScale(0))
.text("CZ 2013");
heightleg = x0Scale(groups.length * h['w']*1.15*h['g']);
var svgleg = d3.select("#legend").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", heightleg + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
groups.sort(function(x,y) {
if (x.n > y.n) return -1;
if (x.n < y.n) return 1;
return 0;
});
var iconsleg = svgleg.selectAll(".iconleg")
.data(groups)
.enter().append("text")
.attr('font-family', 'FontAwesome')
.attr('font-size',x0Scale(h['w']*1.15))
.attr('fill', function(d) {return d.color;})
.attr('text-anchor',"middle")
.attr('class', 'shadow')
.attr('x',x0Scale(h['w']*1.15))
.attr('y',function(d,i) {return (i+1)*x0Scale(h['w']*1.15);})
.text('\uf007');
var textleg = svgleg.selectAll(".textleg")
.data(groups)
.enter().append("text")
.attr('font-family', 'sans-serif')
.attr('font-size',x0Scale(h['w']*0.9))
.attr('x',x0Scale(2*h['w']*1.15))
.attr('y',function(d,i) {return (i+1)*x0Scale(h['w']*1.15);})
.text(function(d){return d.name + ' (' + d.n + ')'});
</script>
</body>
</html>
algorithm.py
import math
import csv
import numpy as np
import timeit
start = timeit.default_timer()
n0=751
optim = {}
def loss1(g):
return (g-1.2)*(g-1.2)
def loss2(w,s,n):
n0 = sum(n)
l2 = 0
i = 0
ln = len(n)
for item in n:
if s[i] > 0:
l2 = l2 + (s[i] - 0.1*w)*(s[i] - 0.1*w)/ln
else:
l2 = l2 + 10
i = i + 1
return l2
def lossf(w,g,s,n):
l1 = loss1(g)
l2 = loss2(w,s,n)
l = l1 + l2 + (l1 - l2)*(l1 - l2)
return l
def ss(w,g,n):
s = [0]*len(n)
i = 0
for item in n:
s[i] = (math.pi/w + math.pi*i*g-n[i])/(n[i] - 1)
i = i + 1
return s
def nmax(k,n):
return math.floor(n-((k-1)*k/2))/k
def nrow(n):
return {'max':round(math.sqrt(n)*3/4),'min':round(max(math.sqrt(n)/4,1))}
def grid(n):
out = []
for k in np.arange (0.01,1,0.01):
for kk in np.arange (1.15,1.25,0.01):
g = kk
w = k
s = ss(w,g,n)
l1 = loss1(g)
l2 = loss2(w,s,n)
l = l1 + l2 + (l1-l2)*(l1-l2)
try:
if l < mmin:
out = [w,g]
mmin = l
except:
out = [w,g]
mmin = l
return {'w':out[0],'g':out[1],'loss':mmin}
def go(level,n,nrows,n0):
global optim
global ll
while level < (nrows-1):
if level > 0:
if (nrows>1):
q = 2*(n0-nrows*n[0])/(nrows-1)/nrows
jmin = math.floor(n[0] + level*q - 0.5)
jmax = math.ceil(n[0] + level*q + 0.5)
else:
jmin = max(level+2+round(sqrt(level)),n[level-1]+1)
jmax = int(nmax(nrows-level,n0-sum(n[0:level]))+1)
else:
jmin = max(level+2,n[level-1]+1)
jmax = int(nmax(nrows-level,n0-sum(n[0:level]))+1)
for j in range(jmin,jmax):
n[level] = j
go(level+1,n,nrows,n0)
return False
n[level] = n0-sum(n)
opt = grid(n)
try:
if ll > opt['loss']:
optim = opt.copy()
optim['n'] = n.copy()
ll = optim['loss']
except:
optim = opt.copy()
optim['n'] = n.copy()
ll = optim['loss']
n[level] = 0
return True
nr = nrow(n0)
for k in range(nr['min'],nr['max']+1):
n = [0]*k
optim = {}
ll = 100000000
go(0,n,k,n0)
print("final optim:",optim)
print("time:",timeit.default_timer() - start)
legend.svg
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg width="600" height="216.84957789716043" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><style type="text/css"><![CDATA[
.axis path, .axis line { fill: none; stroke: rgb(0, 0, 0); shape-rendering: crispedges; }
path, line { stroke: rgb(187, 187, 187); stroke-width: 1px; }
text.shadow { stroke: rgb(128, 128, 128); stroke-width: 1px; opacity: 0.9; }
.half { fill: rgb(136, 136, 136); opacity: 0.5; }]]></style></defs><g transform="translate(0,0)"><text font-family="FontAwesome" font-size="25.81542594013814" fill="orange" text-anchor="middle" class="shadow" x="25.81542594013814" y="25.81542594013814"></text><text font-family="FontAwesome" font-size="25.81542594013814" fill="aqua" text-anchor="middle" class="shadow" x="25.81542594013814" y="51.63085188027628"></text><text font-family="FontAwesome" font-size="25.81542594013814" fill="red" text-anchor="middle" class="shadow" x="25.81542594013814" y="77.44627782041442"></text><text font-family="FontAwesome" font-size="25.81542594013814" fill="purple" text-anchor="middle" class="shadow" x="25.81542594013814" y="103.26170376055256"></text><text font-family="FontAwesome" font-size="25.81542594013814" fill="blue" text-anchor="middle" class="shadow" x="25.81542594013814" y="129.0771297006907"></text><text font-family="FontAwesome" font-size="25.81542594013814" fill="pink" text-anchor="middle" class="shadow" x="25.81542594013814" y="154.89255564082885"></text><text font-family="FontAwesome" font-size="25.81542594013814" fill="yellow" text-anchor="middle" class="shadow" x="25.81542594013814" y="180.707981580967"></text><text font-family="sans-serif" font-size="20.20337682271681" x="51.63085188027628" y="25.81542594013814">ČSSD (50)</text><text font-family="sans-serif" font-size="20.20337682271681" x="51.63085188027628" y="51.63085188027628">ANO (47)</text><text font-family="sans-serif" font-size="20.20337682271681" x="51.63085188027628" y="77.44627782041442">KSČM (33)</text><text font-family="sans-serif" font-size="20.20337682271681" x="51.63085188027628" y="103.26170376055256">TOP 09 (26)</text><text font-family="sans-serif" font-size="20.20337682271681" x="51.63085188027628" y="129.0771297006907">ODS (16)</text><text font-family="sans-serif" font-size="20.20337682271681" x="51.63085188027628" y="154.89255564082885">Úsvit (14)</text><text font-family="sans-serif" font-size="20.20337682271681" x="51.63085188027628" y="180.707981580967">KDU-ČSL (14)</text></g></svg>
plasy.svg
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg width="600" height="300" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><style type="text/css"><![CDATA[
.axis path, .axis line { fill: none; stroke: rgb(0, 0, 0); shape-rendering: crispedges; }
path, line { stroke: rgb(187, 187, 187); stroke-width: 1px; }
text.shadow { stroke: rgb(128, 128, 128); stroke-width: 1px; opacity: 0.9; }
.half { fill: rgb(136, 136, 136); opacity: 0.5; }]]></style></defs><g transform="translate(0,0)"><path d="M-270.02845244780104,-130.70820505479136A300,300 0 0,1 130.70820505479136,-270.02845244780104L0,0Z" transform="translate(300,300)" class="half"></path><text font-family="FontAwesome" font-size="80.17518770110831" fill="red" text-anchor="middle" class="shadow" x="85.75726010756388" y="265.29090179034534" transform="rotate(-80.7975644468734,85.75726010756388,265.29090179034534)"></text><text font-family="FontAwesome" font-size="80.17518770110831" fill="red" text-anchor="middle" class="shadow" x="170.43395001771813" y="265.532639628331" transform="rotate(-75.1030973265986,170.43395001771813,265.532639628331)"></text><text font-family="FontAwesome" font-size="80.17518770110831" fill="orange" text-anchor="middle" class="shadow" x="110.91854020735812" y="193.45013065315834" transform="rotate(-60.598173335154996,110.91854020735812,193.45013065315834)"></text><text font-family="FontAwesome" font-size="80.17518770110831" fill="orange" text-anchor="middle" class="shadow" x="205.09432635105534" y="205.2990319572094" transform="rotate(-45.06185839595915,205.09432635105534,205.2990319572094)"></text><text font-family="FontAwesome" font-size="80.17518770110831" fill="orange" text-anchor="middle" class="shadow" x="159.33809073878794" y="134.71569833122072" transform="rotate(-40.39878222343668,159.33809073878794,134.71569833122072)"></text><text font-family="FontAwesome" font-size="80.17518770110831" fill="orange" text-anchor="middle" class="shadow" x="225.05998666471191" y="96.31232856048376" transform="rotate(-20.19939111171834,225.05998666471191,96.31232856048376)"></text><text font-family="FontAwesome" font-size="80.17518770110831" fill="yellow" text-anchor="middle" class="shadow" x="265.2529527130464" y="170.50867618315183" transform="rotate(-15.02061946531974,265.2529527130464,170.50867618315183)"></text><text font-family="FontAwesome" font-size="80.17518770110831" fill="yellow" text-anchor="middle" class="shadow" x="300" y="82.96388988201645" transform="rotate(0,300,82.96388988201645)"></text><text font-family="FontAwesome" font-size="80.17518770110831" fill="violet" text-anchor="middle" class="shadow" x="334.74704728695355" y="170.50867618315183" transform="rotate(15.020619465319712,334.74704728695355,170.50867618315183)"></text><text font-family="FontAwesome" font-size="80.17518770110831" fill="violet" text-anchor="middle" class="shadow" x="374.94001333528803" y="96.31232856048376" transform="rotate(20.19939111171834,374.94001333528803,96.31232856048376)"></text><text font-family="FontAwesome" font-size="80.17518770110831" fill="blue" text-anchor="middle" class="shadow" x="440.66190926121214" y="134.71569833122084" transform="rotate(40.39878222343669,440.66190926121214,134.71569833122084)"></text><text font-family="FontAwesome" font-size="80.17518770110831" fill="blue" text-anchor="middle" class="shadow" x="394.9056736489447" y="205.2990319572094" transform="rotate(45.061858395959156,394.9056736489447,205.2990319572094)"></text><text font-family="FontAwesome" font-size="80.17518770110831" fill="blue" text-anchor="middle" class="shadow" x="489.08145979264185" y="193.4501306531584" transform="rotate(60.598173335155025,489.08145979264185,193.4501306531584)"></text><text font-family="FontAwesome" font-size="80.17518770110831" fill="blue" text-anchor="middle" class="shadow" x="429.5660499822818" y="265.532639628331" transform="rotate(75.1030973265986,429.5660499822818,265.532639628331)"></text><text font-family="FontAwesome" font-size="80.17518770110831" fill="blue" text-anchor="middle" class="shadow" x="514.242739892436" y="265.2909017903453" transform="rotate(80.79756444687337,514.242739892436,265.2909017903453)"></text><text font-family="sans-serif" font-size="31.372899535216302" font-weight="bold" text-anchor="middle" fill="#444" x="300" y="300">PLASY '10-'14</text></g></svg>