Quelltext Wahlrechner

Quell­text (Stand 20.12.2025) – Till Westermayer

<form id="form1"><canvas id="canvas" width="360" height="360"></canvas>
<table>
<tbody>
<tr>
<td colspan="5" align=""><output name="msg"></output></td>
</tr>
<tr valign="top">
<td>Partei</td>
<td>Koalition</td>
<td width="20%">Direkt-mandate</td>
<td width="20%">Prozent</td>
<td width="30%">Sitze</td>
</tr>
<tr>
<td><input name="pa1" type="text" value="GRÜNE" /></td>
<td><input name="k1" type="checkbox" value="" /></td>
<td><input name="d1" type="text" value="58" /></td>
<td><input name="pr1" type="text" value="32.6" /></td>
<td><output name="s1">-</output></td>
</tr>
<tr>
<td><input name="pa2" type="text" value="CDU" /></td>
<td><input name="k2" type="checkbox" value="" /></td>
<td><input name="d2" type="text" value="12" /></td>
<td><input name="pr2" type="text" value="24.1" /></td>
<td><output name="s2">-</output></td>
</tr>
<tr>
<td><input name="pa3" type="text" value="SPD" /></td>
<td><input name="k3" type="checkbox" value="" /></td>
<td><input name="d3" type="text" value="0" /></td>
<td><input name="pr3" type="text" value="11.0" /></td>
<td><output name="s3">-</output></td>
</tr>
<tr>
<td><input name="pa4" type="text" value="FDP" /></td>
<td><input name="k4" type="checkbox" value="" /></td>
<td><input name="d4" type="text" value="0" /></td>
<td><input name="pr4" type="text" value="10.5" /></td>
<td><output name="s4">-</output></td>
</tr>
<tr>
<td><input name="pa5" type="text" value="AFD" /></td>
<td><input name="k5" type="checkbox" value="" /></td>
<td><input name="d5" type="text" value="0" /></td>
<td><input name="pr5" type="text" value="9.7" /></td>
<td><output name="s5">-</output></td>
</tr>
<tr>
<td><input name="pa6" type="text" value="LINKE" /></td>
<td><input name="k6" type="checkbox" value="" /></td>
<td><input name="d6" type="text" value="0" /></td>
<td><input name="pr6" type="text" value="3.6" /></td>
<td><output name="s6">-</output></td>
</tr>
<tr>
<td colspan="2"></td>
<td><output name="sumd"></output></td>
<td><output name="sumpr"></output></td>
<td><output name="sums"></output></td>
</tr>
</tbody>
</table>
<button class="styled" name="szen1" type="button">Szenario 1
Wahl 2021</button> <button class="styled" name="szen2" type="button">Szenario 2
BWTrend 10/25</button>

</form><script>
const form = document.getElementById("form1");
const pa = [form.elements["pa1"], form.elements["pa2"], form.elements["pa3"], form.elements["pa4"], form.elements["pa5"], form.elements["pa6"]];
const pr = [form.elements["pr1"], form.elements["pr2"], form.elements["pr3"], form.elements["pr4"], form.elements["pr5"], form.elements["pr6"]];
const d = [form.elements["d1"], form.elements["d2"], form.elements["d3"], form.elements["d4"], form.elements["d5"], form.elements["d6"]];
const s = [form.elements["s1"], form.elements["s2"], form.elements["s3"], form.elements["s4"], form.elements["s5"], form.elements["s6"]];
const k = [form.elements["k1"], form.elements["k2"], form.elements["k3"], form.elements["k4"], form.elements["k5"], form.elements["k6"]];
const szen1 = form.elements["szen1"];
const szen2 = form.elements["szen2"];
var calc_sitze = [];
const sumpr = form.elements["sumpr"];
const sumd = form.elements["sumd"];
const sums = form.elements["sums"];
const msg = form.elements["msg"];
function updateError(m) { 
// setzt die ausgegebenen Sitzwerte auf 0 und gibt die Fehlernachricht m aus
msg.value=m;
for (let i = 0; i < 6; i++) {
s[i].value = "-";
}
sums.value="-"
}
var warning = "" // wird von SLS gesetzt - Hinweise auf Rechnenfehler, die angezeigt werden sollen
function SLS(prozente, sitze_soll, dm) {
// gibt ein Array mit den nach Sainte Lague Schepers zu vergebenden Sitzen zurück, das für das Array prozente und die sitze_soll die Verteilung berechnet
// ZU CHECKEN: Umsetzung des Direktmandateabzugs (soll müsste eigentlich sinken, wenn es Direktmandate bei <5% gibt), gleiche Höchstzahlen/Losverfahren implementieren 
// - bei Ausgleich bei letzer Höchstzahl Sitze an alle
warning = ""
var hoechstzahlschema = []
var index = []
var count = []
var primax = 0;
var primax_index = NaN;
var extra_dm = 0; // außerordentliche Direktmandate ohne landesweite Deckungen, werden von Soll abgezogen
for (let i=0; i< prozente.length; i++) {
// initialisierung des höchstzahlschemas (prozentsatz oder 0, wenn <5%)
let pri = prozente[i];
if (isNaN(pri) || pri<5.0) {
if (pri < 5.0 && dm[i]>0) {
extra_dm += dm[i];
}
pri = 0;
}
if (pri > primax) {
primax = pri;
primax_index = i;
}
hoechstzahlschema.push(pri)
index.push(0);
count.push(0);
}
sitze_soll -= extra_dm; // Abzug der außerordentlichen Direktmandate
//console.log(hoechstzahlschema);
for(let i=0; i<sitze_soll; i++) {
// Verteilung der Sitze
count[primax_index] ++;
index[primax_index] ++;
//console.log("Sitz "+(i+1)+" geht an "+primax_index+" mit Höchstzahl "+primax);
primax = 0;
primax_index = NaN;
let multiple = [];
for(let j=0; j<index.length; j++) { 
let m = hoechstzahlschema[j]/(index[j]*2+1); // SLS 
//console.log(j," : ", m); 
if (m > primax) {
primax = m;
primax_index = j;
} else {
if (i==sitze_soll-1 && m>0 && m == primax) {
//console.log("identische Höchstzahlen bei Sitz "+i)
multiple.push(j);
}
}
}
if (isNaN(primax_index)) {
updateError("Fehler beim Durchführen des Höchstzahlverfahrens.")
console.log("primax jetzt "+primax+" index " + primax_index);
return [];
}
if (multiple.length > 0) {
if (sitze_soll > 120) {
// Alle zusätzlichen Sitze vergeben.
for (let n=0;n<multiple.length;n++) { 
count[multiple[n]]++; 
} 
} else { 
warning = "Losentscheid um den letzten Sitz bei Sollzahl 120, hier nicht abgebildet. "; 
} 
//console.log("Letzter Sitz mehrfach vergeben."); 
} 
} 
if (extra_dm > 0) {
for(let i=0;i<count.length;i++) { 
// Sonderfall Direktmandate 
if (count[i] == 0 && dm[i]>0) {
count[i] = dm[i];
}
}
}
return count;
}
function calcSeats() {
// berechnet die Sitzzahl für den Landtag BW
var sitze_gesamt = 0;
var sitze_soll = 120;
// Prozente auslesen
var prozente = [];
for (let i = 0; i < pr.length; i++) {
let n = parseFloat(pr[i].value);
if (isNaN(n)) {
n = 0
} 
prozente.push(n);
}
var direktmandate = [];
var sitze = [];
for (let i = 0; i < d.length; i++) {
let n = parseInt(d[i].value);
if (isNaN(n)) {
n = 0;
}
sitze.push(n);
direktmandate.push(n); 
sitze_gesamt += n;
}
// sitze_gesamt sollte nun 70 sein
var sitze_ideal = [];
var alles_passt = true;
// schleife, um größe zu erhöhen, bis alles passt
while(true) {
msg.value = "Probiere " + sitze_soll + " aus."
sitze_ideal = SLS(prozente, sitze_soll, direktmandate)
alles_passt = true;
for (let i = 0;i <sitze.length;i++) { 
// hat eine partei mehr direktmandate als nach SLS vergeben? 
if (sitze[i] > sitze_ideal[i]) {
alles_passt = false;
} 
}
if (alles_passt) {
calc_sitze = sitze_ideal;
return sitze_ideal;
}
// wir erhöhen die sitzzahl um eins und probieren es noch einmal 
sitze_soll += 1;
if (sitze_soll>300) {
updateError("Fehler - Soll-Sitzzahl über 300 - Abbruch");
return [];
}
}
}
function updateResult() { 
// berechnet aus den eingebenen Werten die Sitzverteilung
msg.value="Berechne Werte ..."
var sm = 0; // Prozentsumme unter Berücksichtigung 5%-Hürde
var sumsv = 0; // Summe der vergebenen Sitze
var sumprv = 0; // Prozentsumme ohne Berücksichtigung 5%-Hürde
var sumdv = 0; // Summe der Direktmandate
var prtemp = 0;
var sdvtemp = 0;
// Berechnung diverser Summen
for (let i = 0; i < pr.length; i++) { 
prtemp = parseFloat(pr[i].value); 
if (isNaN(prtemp)) { 
prtemp = 0.0 
} 
if (prtemp>=5.0) {
sm += prtemp;
}
sumprv += prtemp;
sdvtemp = parseInt(d[i].value);
if (isNaN(sdvtemp)) {
sdvtemp = 0
}
sumdv += sdvtemp;
}
sumd.value = sumdv;
sumpr.value = sumprv.toFixed(1);
if (sumprv > 100) {
updateError("Prozentsumme über 100!");
return;
}
if (sumdv != 70) {
updateError("Direktmandate müssen genau 70 sein!");
return;
} 
if ((! isNaN(sm)) && (sm>0)) {
var sls = calcSeats();
sumsv = 0
for (let i = 0; i < sls.length; i++) {
sumsv += parseInt(sls[i]);
}
for (let i = 0; i < sls.length; i++) {
let prx = parseFloat(pr[i].value);
if (isNaN(prx) || prx<5.0) {
prx = 0.0
}
s[i].value = sls[i] + " (" + (parseInt(sls[i])/sumsv*100).toFixed(1) + "% Sitze vs. " + (prx/sm*100).toFixed(1) + "% gültige Prozent)";
}
sums.value = sumsv;
if (sumsv % 2 == 0) {
var majo = (sumsv/2+1).toFixed(0);
} else {
var majo = ((sumsv+1)/2).toFixed(0);
} 
msg.value=warning+"Insgesamt " + sums.value + " Sitze. Mehrheit bei " + majo + " Sitzen. "
var koam = 0;
var koap = "";
for(let i=0;i<k.length;i++) { 
if (k[i].checked == true) { 
koam += parseInt(sls[i]); 
koap += pa[i].value + " "; 
} 
} 
if (koam>0) {
if (koam >= majo) {
msg.value = msg.value + "Mehrheit für " + koap + " (" + koam + " Sitze).";
} else {
msg.value = msg.value + "Keine Mehrheit für " + koap + " (" + koam + " Sitze).";
}
}
} else {
updateError("Fehler bei der Eingabe");
}
draw();
}
function updateData1() {
// Werte auf Szenario 1 - Landtagswahl 2021 - setzen
pa[0].value = "GRÜNE"; pa[1].value = "CDU"; pa[2].value = "SPD"; pa[3].value = "FDP"; pa[4].value = "AFD"; pa[5].value="LINKE";
d[0].value = "58"; d[1].value = "12"; d[2].value = "0"; d[3].value = "0"; d[4].value = "0"; d[5].value="0";
pr[0].value = "32.6"; pr[1].value = "24.1"; pr[2].value = "11.0"; pr[3].value = "10.5"; pr[4].value = "9.7"; pr[5].value="3.6";
updateResult();
}
function updateData2() {
// Werte auf Szenario 2 - BW-Trend 10/2025 - setzen
pa[0].value = "GRÜNE"; pa[1].value = "CDU"; pa[2].value = "SPD"; pa[3].value = "FDP"; pa[4].value = "AFD"; pa[5].value="LINKE";
d[0].value = "25"; d[1].value = "40"; d[2].value = "0"; d[3].value = "0"; d[4].value = "5"; d[5].value="0";
pr[0].value = "20"; pr[1].value = "29"; pr[2].value = "10"; pr[3].value = "5"; pr[4].value = "21"; pr[5].value="7";
updateResult();
}
const extracolors = ["orange","lightblue","beige","lightgray","lightgreen","lime"]
function draw() {
// Tortendiagramm Sitze
// Farben nach Partei zuordnen
var color = [];
var tcolor = [];
let n=0;
for(let i=0;i<pa.length;i++) { 
party = pa[i].value.toUpperCase(); 
switch (party) { 
case "BÜNDNIS 90/DIE GRÜNEN": 
case "DIE GRÜNEN": 
case "GRÜNE": color.push("green"); tcolor.push("white"); break; 
case "CDU": color.push("#303030"); tcolor.push("white"); break; 
case "SPD": color.push("red"); tcolor.push("white"); break; 
case "FDP": case "FDP/DVP": color.push("yellow"); tcolor.push("black"); break; 
case "AFD": color.push("cyan"); tcolor.push("black"); break; 
case "LINKE": color.push("#601590"); tcolor.push("white"); break; 
default: color.push(extracolors[n%extracolors.length]); tcolor.push("black"); n++; 
} 
} const canvas = document.getElementById("canvas"); 
const ctx = canvas.getContext("2d"); 
ctx.beginPath(); 
const x = 180; // x coordinate 
const y = 180; // y coordinate 
const radius = 175; // Arc radius 
var startAngle = 0; // Starting point on circle 
var endAngle = (Math.PI/2)*3; // End point on circle 
const counterclockwise = 0; // clockwise or counterclockwise 
const sumstemp = parseInt(sums.value); 
var rx, ry, tx, ty; 
if (sumstemp>0) {
ctx.font = "14px sans-serif";
ctx.textAlign = "center";
ctx.textBaseline = "middle"; 
for (let i = 0; i < calc_sitze.length; i++) { if (calc_sitze[i]>0) {
startAngle = endAngle;
endAngle = startAngle + (Math.PI/180)*(360/sumstemp)*parseInt(calc_sitze[i]);
rx = x + radius * Math.cos(startAngle);
ry = y + radius * Math.sin(startAngle);
tx = x + 0.67*radius * Math.cos((startAngle+endAngle)/2);
ty = y + 0.67*radius * Math.sin((startAngle+endAngle)/2);
//console.log(i + " Bogen von " + startAngle.toFixed(1) + " nach " + endAngle.toFixed(1));
ctx.beginPath();
ctx.moveTo(x,y);
ctx.lineTo(rx,ry);
ctx.arc(x, y, radius, startAngle, endAngle, counterclockwise);
ctx.lineTo(x,y);
ctx.fillStyle = color[i];
ctx.fill();
ctx.strokeStyle = "white";
ctx.stroke();
ctx.fillStyle = tcolor[i];
ctx.fillText(pa[i].value, tx, ty);
ctx.fillText(calc_sitze[i], tx, ty+14);
}
}
}
}
szen1.addEventListener("click", updateData1);
szen2.addEventListener("click", updateData2);
form.addEventListener("input", updateResult);
updateResult();
</script>