Greasy Fork is available in English.

Koalitionenrechner - wahlrecht.de

3/21/2024, 10:00:16 PM

// ==UserScript==
// @name        Koalitionenrechner - wahlrecht.de
// @namespace   Violentmonkey Scripts
// @match       https://www.wahlrecht.de/umfragen/
// @grant       none
// @version     3.0
// @author      Sidem
// @description 3/21/2024, 10:00:16 PM
// ==/UserScript==
/* jshint esversion: 6 */

window.addEventListener('load', function () {
  var script = document.createElement('script');
  script.src = 'https://cdn.jsdelivr.net/npm/chart.js@3.5.1/dist/chart.min.js';
  document.head.appendChild(script);
  let setNewChart = () => { };
  let setNewCoalitionChart = () => { };
  let seatFactor = 1.0;
  let data = [
    { id: 'cdu', name: 'CDU/CSU', icon: '⚫️', color: '#000000FF', votes: 0, projectedSeats: 0, logoUrl: '<img src="https://i.ibb.co/tYm4PG9/CDUCSU.png" alt="CDUCSU" border="0">',   colorUri: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAAA1BMVEUAAACnej3aAAAAC0lEQVQI12MgEQAAADAAAWV61nwAAAAASUVORK5CYII=' },
    { id: 'spd', name: 'SPD', icon: '🔴', color: '#E3000FFF', votes: 0, projectedSeats: 0, logoUrl: '<img src="https://i.ibb.co/LScLCYC/SPD.png" alt="SPD" border="0">',             colorUri: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAAA1BMVEXjAA+cYU6yAAAAC0lEQVQI12MgEQAAADAAAWV61nwAAAAASUVORK5CYII=' },
    { id: 'gru', name: 'GRÜNE', icon: '🟢', color: '#64A12DFF', votes: 0, projectedSeats: 0, logoUrl: '<img src="https://i.ibb.co/M6cx5K3/GRUENE.png" alt="GRUENE" border="0">',     colorUri: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAAA1BMVEVkoS0aZ4/7AAAAC0lEQVQI12MgEQAAADAAAWV61nwAAAAASUVORK5CYII=' },
    { id: 'fdp', name: 'FDP', icon: '🟡', color: '#FFED00FF', votes: 0, projectedSeats: 0, logoUrl: '<img src="https://i.ibb.co/m093TBw/FDP.png" alt="FDP" border="0">',             colorUri: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAAA1BMVEX/7QDyMoSWAAAAC0lEQVQI12MgEQAAADAAAWV61nwAAAAASUVORK5CYII=' },
    { id: 'lin', name: 'LINKE', icon: '🟣', color: '#FF0000FF', votes: 0, projectedSeats: 0, logoUrl: '<img src="https://i.ibb.co/Mp4TFzX/LINKE.png" alt="LINKE" border="0">',       colorUri: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAAA1BMVEX/AAAZ4gk3AAAAC0lEQVQI12MgEQAAADAAAWV61nwAAAAASUVORK5CYII=' },
    { id: 'afd', name: 'AfD', icon: '🔵', color: '#009DE0FF', votes: 0, projectedSeats: 0, logoUrl: '<img src="https://i.ibb.co/3hzmWRr/AfD.png" alt="AfD" border="0">',             colorUri: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAAA1BMVEUAneDDnyv1AAAAC0lEQVQI12MgEQAAADAAAWV61nwAAAAASUVORK5CYII=' },
    { id: 'bsw', name: 'BSW', icon: '🟠', color: '#FF8800FF', votes: 0, projectedSeats: 0, logoUrl: '<img src="https://i.ibb.co/3hzmWRr/AfD.png" alt="BSW" border="0">',             colorUri: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAAA1BMVEX/iADquBt0AAAAC0lEQVQI12MgEQAAADAAAWV61nwAAAAASUVORK5CYII=' },
    { id: 'son', name: 'Sonstige', icon: '⚪️', color: '#666666FF', votes: 0, projectedSeats: 0, logoUrl: '',  colorUri: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAAA1BMVEVmZmZ8VoIEAAAAC0lEQVQI12MgEQAAADAAAWV61nwAAAAASUVORK5CYII=' }
  ];
  let voteData = [];
  let coalitionData = [];

  function getCombinations(array) {
    var result = [];
    for (var i = 1; i < (1 << array.length); i++) {
      var subset = [];
      for (var j = 0; j < array.length; j++)
        if (i & (1 << j))
          subset.push(array[j]);
      result.push(subset);
    }
    return result;
  }

  function cleanVote(str) {
    if(str.includes('\n')) {
      let split = str.split('\n');
      return cleanVote(split[0].split(' ')[1])+cleanVote(split[1].split(' ')[1]);
    }
    if(str.includes('–')) return 0;
    return parseFloat(str.replace(',', '.').replace(' %', ''));
  }

  function getPartyByIdentifier(identifier) {
    for (let p of voteData) {
      if (p.name == identifier || p.color == identifier || p.icon == identifier || p.id == identifier) {
        return p;
      }
    }
    return { id: 'son', name: 'Sonstige', icon: '⚪️', color: '#666666', votes: 0 };
  }

  let allPossibleCoalitions = getCombinations(['⚫️', '🔴', '🟣', '🟡', '🟢', '🔵', '🟠']);
  allPossibleCoalitions = allPossibleCoalitions.filter((el) => { return (el.length > 1 && el.length < 5) }); // filter to only 2-4 party coalitions
  //let coalitions = allPossibleCoalitions;

  let coalitions = allPossibleCoalitions.filter((el) => { // filter exclusions, eg far right wont have a coalition with far left
    if (el.includes('🔵') && el.includes('🟢')) return false;
    if (el.includes('🔵') && el.includes('🔴')) return false;
    if (el.includes('🔵') && el.includes('🟣')) return false;
    //if (el.includes('🔵') && el.includes('⚫️')) return false;
    //if (el.includes('🔵') && el.includes('🟡')) return false;
    if (el.includes('🟣') && el.includes('🟡')) return false;
    if (el.includes('🟣') && el.includes('⚫️')) return false;
    return true;
  });

  function isPermutation(a, b) {
    return b.filter(x => !a.includes(x)).length === 0 && a.length == 3;
  }

  function coalitionSymbol(coalition) {
    if (isPermutation(coalition, ['⚫️', '🔴', '🟢'])) return "<img src='https://flagcdn.com/w20/ke.png' alt='🇰🇪' title='Kenia'>";
    if (isPermutation(coalition, ['⚫️', '🔴', '🟡'])) return "<img src='https://flagcdn.com/w20/de.png' alt='🇩🇪' title='Deutschland'>";
    if (isPermutation(coalition, ['⚫️', '🟡', '🟢'])) return "<img src='https://flagcdn.com/w20/jm.png' alt='🇯🇲' title='Jamaika'>";
    if (isPermutation(coalition, ['🟣', '🔴', '🟢'])) return "<img src='https://flagcdn.com/w20/by.png' alt='🇧🇾' title='Weissrussland'>";
    if (isPermutation(coalition, ['⚫️', '🟡', '🔵'])) return "<img src='https://flagcdn.com/w20/bs.png' alt='🇧🇸' title='Bahamas'>";
    if (isPermutation(coalition, ['🟡', '🔴', '🟢'])) return "🚦";
    return "";
  }

  function getCoalitions() {
    let str = "<strong>Anteil der Sitze im Bundestag</strong>";
    let results = [];
    for (let coalition of coalitions) {
      let currentCoalitionVotes = 0;
      let containsBelow = false;
      coalition.sort((p1, p2) => { return getPartyByIdentifier(p2).votes - getPartyByIdentifier(p1).votes });
      for (let p of coalition) {
        if(currentCoalitionVotes >= 50.0) containsBelow = true;
        currentCoalitionVotes += getPartyByIdentifier(p).votes*seatFactor;
        if (getPartyByIdentifier(p).votes < 5.0) containsBelow = true;
      }

      if(!containsBelow) results.push({ coalition: coalition.join(""), parties: coalition, votes: currentCoalitionVotes });
    }
    results.sort((a, b) => { return b.votes - a.votes; });
    let line = false;
    let weight = "bold;";
    for (let c of results) {
      if (!line && c.votes < 50) {
        line = true;
        weight = '100;';
      }
      str += "<p style='margin: 0px; font-size: 1rem; font-weight: " + weight + "'><strong style='display: inline-block; vertical-align: middle; width: 64px;'>" + c.parties.map((e) => { return '<img class="partyColor" src="'+getPartyByIdentifier(e).colorUri+'" alt="'+getPartyByIdentifier(e).icon+'" title="'+getPartyByIdentifier(e).name+'" />'; }).join("") + "</strong>" + c.votes.toFixed(1) + "% " + coalitionSymbol(c.parties) + "</p>";
    }
    coalitionData = results;
    //str += "<button id='copyCoalitionBtn'>Copy</button>";
    coalitionBox.innerHTML = str;
    let copyBtn = document.createElement('button');
    copyBtn.innerText = "Copy";
    copyBtn.onclick = (e) => {
      navigator.clipboard.writeText(getCoalitionString(coalitionBox)).then(() => {
            console.log("success");
        }, function () {
            console.log("failed writing clipboard");
        });
    };
    coalitionBox.appendChild(copyBtn);
    setNewCoalitionChart();
  }

  const getCoalitionString = (container) => {
    let str = "";
    for (let p of container.childNodes) {
      for (let item of p.childNodes) {
        if(item.nodeName == 'STRONG') {
          for (let color of item.childNodes) {
            str += color.alt;
          }
        } else if (item.nodeName == '#text' && item.data != 'Copy') {
          str += item.data;
        } else if (item.nodeName == 'IMG') {
          str += item.alt;
        }
      }
      str += "\n";
    }
    return str;
  };

  let getBelowFivePercentage = () => {
    let total = getPartyByIdentifier('son').votes;
    let partyIds = ["cdu", "spd", "gru", "fdp", "lin", "afd", "bsw"];
    for (let id of partyIds) {
      let thisVotes = getPartyByIdentifier(id).votes;
      if(thisVotes < 5.0) total += thisVotes;
    }
    return total;
  };

  let getChart = (e) => {
    let colDataElements = document.getElementsByClassName(e.target.classList[e.target.classList.length - 1]);
    let i = 0;
    voteData = [];
    for (let el of colDataElements) {
      voteData.push({ ...data[i], votes: cleanVote(el.innerText) })
      i++;
    }
    voteData.sort((a, b) => { return b.votes - a.votes; });
    seatFactor = 1/((100-getBelowFivePercentage())/100);
    getCoalitions();
    setNewChart();
  };


  let getCoalitionDatasets = () => {
    let datasets = [];
    let numberset = [];
    for (let party of voteData) {
      numberset = [];
      if (party.name != 'Sonstige' && party.votes >= 5.0) {
        for (let coalition of coalitionData) {
          if (coalition.parties.includes(party.icon)) {
            numberset.push(party.votes*seatFactor);
          } else {
            numberset.push(0);
          }
        }
        datasets.push({ label: party.name, data: numberset, backgroundColor: party.color });
      }
    }
    return datasets;
  }

  let partyIds = ["cdu", "spd", "gru", "fdp", "lin", "afd", "bsw", "son"];
  let rows = document.getElementsByTagName('tr');
  for (let row of rows) {
    if (partyIds.includes(row.id)) {
      let numbers = row.getElementsByTagName('td');
      let n = 0;
      for (let number of numbers) {
        if (n != 0 && n != 9) {
          number.classList.add('col-' + n);
          number.onclick = getChart;
          number.style.cursor = 'pointer';
          number.addEventListener("mouseenter", function (e) {
            let columnClass = e.target.classList[e.target.classList.length - 1];
            let column = document.getElementsByClassName(columnClass);
            for (let item of column) {
              item.style.backgroundColor = 'aqua';
            }
          });
          number.addEventListener("mouseleave", function (e) {
            let columnClass = e.target.classList[e.target.classList.length - 1];
            let column = document.getElementsByClassName(columnClass);
            for (let item of column) {
              item.style.backgroundColor = 'white';
            }
          });
        }
        n++;
      }
    }
  }

  let info = document.getElementById('info');
  let coalitionBox = document.createElement('div');
  coalitionBox.setAttribute('id', 'coalitionBox');
  coalitionBox.style.marginRight = '0px';
  coalitionBox.style.marginTop = '10px';
  coalitionBox.style.float = 'right';
  info.parentNode.insertBefore(coalitionBox, info);
  let table = document.querySelector('.wilko');
  table.style.borderSpacing = '0px';

  script.onload = function () {
    let ctx = document.getElementById('myChart').getContext('2d');
    let myChart = {};

    let ctx2 = document.getElementById('myCoalitionChart').getContext('2d');
    let myCoalitionChart = {};

    setNewChart = () => {
      if (myChart instanceof Chart) {
        myChart.data.labels = voteData.map(a => a.name);
        myChart.data.datasets[0].data = voteData.map(a => a.votes);
        myChart.data.datasets[0].backgroundColor = voteData.map(a => a.color);
        myChart.update();
      } else {
        myChart = new Chart(ctx, {
          type: 'bar',
          data: {
            labels: voteData.map(a => a.name),
            datasets: [{
              label: '% der Stimmen',
              data: voteData.map(a => a.votes),
              backgroundColor: voteData.map(a => a.color),
              borderWidth: 1,
              circumference: 180,
              rotation: 270
            }]
          },
          options: {
            scales: {
              y: {
                suggestedMin: 0,
                //suggestedMax: 50
              }
            }
          }
        });
      }
    };

    setNewCoalitionChart = () => {
      if (myCoalitionChart instanceof Chart) {
        myCoalitionChart.data.labels = coalitionData.map(a => "");
        myCoalitionChart.data.datasets = [...getCoalitionDatasets(), {
          label: '50% Grenze',
          data: coalitionData.map(a => 50),
          borderColor: "#000000",
          backgroundColor: "#000000",
          type: 'line',
          order: 10
        }];
        myCoalitionChart.update();
      } else {
        myCoalitionChart = new Chart(ctx2, {
          type: 'bar',
          data: {
            labels: coalitionData.map(a => ""),
            datasets: [...getCoalitionDatasets(), {
              label: '50% Grenze',
              data: coalitionData.map(a => 50),
              borderColor: "#000000",
              backgroundColor: "#000000",
              type: 'line',
              order: 10
            }]
          },
          options: {
            radius: 0,
            indexAxis: 'y',
            scales: {
              x: {
                suggestedMin: 0,
                suggestedMax: 50,
                stacked: true
              },
              y: {
                stacked: true
              }
            },
            responsive: true,
            plugins: {
              legend: {
                position: 'right',
              },
              title: {
                display: true,
                text: 'Mögliche Koalitionen (% der projezierten Sitze)'
              }
            }
          }
        });
      }

    };
  };
  let chartContainer = document.createElement('div');
  chartContainer.style.width = "75%";
  info.parentNode.insertBefore(chartContainer, info);
  let chartBox = document.createElement('canvas');
  chartBox.id = 'myChart';
  chartContainer.appendChild(chartBox);

  let coalitionChartContainer = document.createElement('div');
  coalitionChartContainer.style.width = "75%";
  info.parentNode.insertBefore(coalitionChartContainer, info);
  let coalitionChartBox = document.createElement('canvas');
  coalitionChartBox.id = 'myCoalitionChart';
  coalitionChartContainer.appendChild(coalitionChartBox);

}, false);