2023-10-01 11:36:12 +00:00
|
|
|
import {BarChart, PieChart, Svg} from 'chartist';
|
2023-09-28 19:49:31 +00:00
|
|
|
import {benchSort, committees, constituencies} from './data';
|
2020-11-08 15:59:50 +00:00
|
|
|
import calculateMandates from './mandates';
|
2023-10-10 10:16:45 +00:00
|
|
|
import constituencyResultsTemplate from './templates/cresults.pug';
|
2020-11-08 15:59:50 +00:00
|
|
|
import constituencyTemplate from './templates/constituency.pug';
|
|
|
|
import tableTemplate from './templates/table.pug';
|
2023-10-10 10:16:45 +00:00
|
|
|
import constituenciesMap from './images/c41.svg';
|
2020-11-08 15:59:50 +00:00
|
|
|
import './styles/styles.css';
|
|
|
|
|
2020-11-20 15:56:48 +00:00
|
|
|
const {location} = window;
|
|
|
|
|
2023-08-19 21:32:51 +00:00
|
|
|
let barChart: BarChart | null = null;
|
|
|
|
let pieChart: PieChart | null = null;
|
2020-11-08 15:59:50 +00:00
|
|
|
|
2023-10-01 11:36:12 +00:00
|
|
|
const displayPercent = (value: number) => `${value.toLocaleString('pl', {
|
|
|
|
minimumFractionDigits: 0,
|
|
|
|
maximumFractionDigits: 1,
|
|
|
|
})}%`;
|
|
|
|
|
2020-11-08 15:59:50 +00:00
|
|
|
export const clearInputs = (): void => {
|
|
|
|
const inputs = document.querySelectorAll<HTMLInputElement>('tr td:nth-child(2) input');
|
|
|
|
inputs.forEach((input) => {
|
|
|
|
input.value = '';
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
export const clearResults = (): void => {
|
2023-08-18 20:42:33 +00:00
|
|
|
document.querySelectorAll<HTMLTableCellElement>('tr td:last-child').forEach((td) => {
|
2020-11-08 15:59:50 +00:00
|
|
|
td.innerHTML = '';
|
|
|
|
});
|
2023-08-19 21:32:51 +00:00
|
|
|
if (barChart) barChart.detach();
|
|
|
|
if (pieChart) pieChart.detach();
|
2023-09-24 16:33:49 +00:00
|
|
|
const urlContainer = document.getElementById('url');
|
|
|
|
if (urlContainer) urlContainer.innerHTML = '';
|
2020-11-08 15:59:50 +00:00
|
|
|
document.getElementById('support-bar-chart')!.innerHTML = '';
|
|
|
|
document.getElementById('division-pie-chart')!.innerHTML = '';
|
2023-09-24 16:33:49 +00:00
|
|
|
const constituencyResultContainer = document.getElementById('constituency-results');
|
2023-10-10 10:16:45 +00:00
|
|
|
if (constituencyResultContainer) constituencyResultContainer.innerHTML = constituencyResultsTemplate();
|
2020-11-20 15:56:48 +00:00
|
|
|
if (location.search) {
|
|
|
|
const urlWithoutSearchString = location.href.split('?')[0];
|
2020-11-08 15:59:50 +00:00
|
|
|
window.history.pushState('', '', urlWithoutSearchString);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
const displayResults = (mandates: number[]) => {
|
|
|
|
mandates.forEach((value, index) => {
|
|
|
|
const committeeId = committees[index].id;
|
2023-08-18 20:42:33 +00:00
|
|
|
const td = document.querySelector<HTMLTableCellElement>(`tr.${committeeId} td:last-child`);
|
2020-11-08 15:59:50 +00:00
|
|
|
if (td) td.textContent = value.toString();
|
2023-10-20 09:15:18 +00:00
|
|
|
if (index === 5 && value > 0 && td?.parentElement) {
|
|
|
|
td.parentElement.style.display = 'table-row';
|
|
|
|
}
|
2020-11-08 15:59:50 +00:00
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
const displayUrl = (support: number[]) => {
|
2023-09-24 16:33:49 +00:00
|
|
|
const container = document.getElementById('url');
|
|
|
|
if (container) {
|
|
|
|
const searchParams = new URLSearchParams();
|
|
|
|
support.forEach((s, i) => {
|
|
|
|
if (s > 0) searchParams.append(committees[i].id, s.toString());
|
|
|
|
});
|
|
|
|
const urlWithoutSearchString = location.href.split('?')[0];
|
|
|
|
const url = `${urlWithoutSearchString}?${searchParams}`;
|
|
|
|
document.getElementById('url')!.innerHTML = `Link do wyników: ${url.link(url)}`;
|
|
|
|
}
|
2020-11-08 15:59:50 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
const displayBarChart = (support: number[]) => {
|
|
|
|
const sortedSupport = committees
|
|
|
|
.map((c, i) => ({
|
|
|
|
label: c.shortName,
|
|
|
|
support: {value: support[i], className: c.id},
|
|
|
|
}))
|
|
|
|
.filter((s) => s.support.value && s.support.value > 0)
|
|
|
|
.sort((a, b) => b.support.value - a.support.value);
|
|
|
|
const chartData = {
|
|
|
|
labels: sortedSupport.map((ss) => ss.label),
|
|
|
|
series: sortedSupport.map((ss) => ss.support),
|
|
|
|
};
|
|
|
|
const chartOptions = {
|
|
|
|
distributeSeries: true,
|
2023-08-20 21:07:58 +00:00
|
|
|
axisY: {
|
2023-10-01 11:36:12 +00:00
|
|
|
labelInterpolationFnc: displayPercent,
|
2023-08-20 21:07:58 +00:00
|
|
|
},
|
2020-11-08 15:59:50 +00:00
|
|
|
};
|
2023-09-28 19:50:02 +00:00
|
|
|
document.getElementById('support-bar-chart')!.classList.add('ct-perfect-fourth');
|
2023-08-19 21:32:51 +00:00
|
|
|
const chart = new BarChart('#support-bar-chart', chartData, chartOptions);
|
|
|
|
chart.on<'draw'>('draw', (data) => {
|
2020-11-08 15:59:50 +00:00
|
|
|
if (data.type === 'bar') {
|
|
|
|
data.element.attr({
|
|
|
|
style: 'stroke-width: 30px',
|
|
|
|
});
|
2023-10-01 11:36:12 +00:00
|
|
|
data.group.append(
|
|
|
|
new Svg(
|
|
|
|
'text',
|
|
|
|
{x: data.x2 + 15, y: data.y2 - 5, 'text-anchor': 'end'},
|
|
|
|
'bar-value',
|
|
|
|
).text(displayPercent((data.value as {y: number}).y)),
|
|
|
|
);
|
2020-11-08 15:59:50 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
return chart;
|
|
|
|
};
|
|
|
|
|
|
|
|
const displayPieChart = (mandates: number[]) => {
|
2023-09-28 19:49:31 +00:00
|
|
|
const commiteesWithMandates = committees
|
2020-11-08 15:59:50 +00:00
|
|
|
.map((c, i) => ({
|
2023-09-28 19:49:31 +00:00
|
|
|
id: c.id,
|
2020-11-08 15:59:50 +00:00
|
|
|
label: c.shortName,
|
|
|
|
mandates: {value: mandates[i], className: c.id},
|
|
|
|
}))
|
2023-09-28 19:49:31 +00:00
|
|
|
.filter((m) => m.mandates.value && m.mandates.value > 0);
|
|
|
|
commiteesWithMandates.sort((a, b) => (benchSort.indexOf(a.id) > benchSort.indexOf(b.id) ? 1 : -1));
|
2020-11-08 15:59:50 +00:00
|
|
|
const chartData = {
|
2023-09-28 19:49:31 +00:00
|
|
|
series: commiteesWithMandates.map((sm) => sm.mandates),
|
2020-11-08 15:59:50 +00:00
|
|
|
};
|
|
|
|
const chartOptions = {
|
|
|
|
donut: true,
|
|
|
|
donutWidth: 60,
|
|
|
|
startAngle: 270,
|
|
|
|
total: 460 * 2,
|
|
|
|
labelInterpolationFnc: (value: number, index: number) => (
|
2023-09-28 19:49:31 +00:00
|
|
|
value < 15 ? '' : `${commiteesWithMandates[index].label} ${value}`
|
2020-11-08 15:59:50 +00:00
|
|
|
),
|
|
|
|
};
|
2023-09-28 19:50:02 +00:00
|
|
|
document.getElementById('division-pie-chart')!.classList.add('ct-perfect-fourth');
|
2023-08-19 21:32:51 +00:00
|
|
|
return new PieChart('#division-pie-chart', chartData, chartOptions);
|
2020-11-08 15:59:50 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
const displayConstituencyResults = () => {
|
|
|
|
const container = document.getElementById('constituency-results');
|
2023-09-24 16:33:49 +00:00
|
|
|
if (container) {
|
2023-10-10 10:16:45 +00:00
|
|
|
container.innerHTML = constituencyResultsTemplate();
|
|
|
|
const constituencyResult = document.getElementById('constituency-result')!;
|
|
|
|
|
|
|
|
const mapObject = document.getElementById('constituencies-map')! as HTMLObjectElement;
|
|
|
|
mapObject.setAttribute('data', constituenciesMap);
|
|
|
|
mapObject.addEventListener('load', () => {
|
|
|
|
const svgDocument = mapObject.contentDocument!;
|
|
|
|
const constituencyNumber = svgDocument.getElementById('constituency-number')!;
|
|
|
|
const constituencyName = svgDocument.getElementById('constituency-name')!;
|
|
|
|
const paths = svgDocument.querySelectorAll<SVGPathElement>('svg path');
|
|
|
|
paths.forEach((path) => {
|
|
|
|
const cid = parseInt(path.dataset.cid!, 10);
|
|
|
|
const constituency = constituencies[cid - 1];
|
|
|
|
const data = (constituency.mandates && constituency.support)
|
|
|
|
? constituency.mandates.map((mandates, committeeIndex) => ({
|
|
|
|
committee: committees[committeeIndex],
|
|
|
|
support: (constituency.support as number[])[committeeIndex],
|
|
|
|
mandates,
|
|
|
|
}))
|
|
|
|
: [];
|
|
|
|
data.sort((a, b) => b.support - a.support);
|
|
|
|
if (data[0].support === data[1].support) {
|
|
|
|
path.style.fill = 'lightgray';
|
|
|
|
} else {
|
|
|
|
path.classList.add(data[0].committee.id);
|
|
|
|
if (data[0].support >= 50) path.classList.add('bright50');
|
|
|
|
else if (data[0].support < 40) path.classList.add('bright30');
|
|
|
|
}
|
|
|
|
|
|
|
|
path.addEventListener('mouseover', (event) => {
|
|
|
|
const pathElement = event.target as SVGPathElement;
|
|
|
|
pathElement.style.stroke = '#444';
|
|
|
|
constituencyNumber.innerHTML = `Okręg nr ${pathElement.dataset.cid}`;
|
|
|
|
constituencyName.innerHTML = pathElement.dataset.cname as string;
|
|
|
|
});
|
|
|
|
path.addEventListener('mouseout', (event) => {
|
|
|
|
const pathElement = event.target as SVGPathElement;
|
|
|
|
pathElement.style.stroke = '#fff';
|
|
|
|
constituencyNumber.innerHTML = '';
|
|
|
|
constituencyName.innerHTML = '';
|
|
|
|
});
|
|
|
|
path.addEventListener('click', (event) => {
|
|
|
|
(event.target as SVGPathElement).style.stroke = '#ccc';
|
|
|
|
constituencyResult.innerHTML = constituencyTemplate({
|
|
|
|
number: cid,
|
|
|
|
name: constituency.name,
|
|
|
|
size: constituency.size,
|
|
|
|
data,
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
2023-09-24 16:33:49 +00:00
|
|
|
});
|
|
|
|
}
|
2020-11-08 15:59:50 +00:00
|
|
|
};
|
|
|
|
|
2022-08-01 20:53:21 +00:00
|
|
|
const validate = (form: HTMLFormElement, inputs: NodeListOf<HTMLInputElement>, support: number[]) => {
|
|
|
|
inputs.forEach((input) => input.setCustomValidity(''));
|
|
|
|
|
|
|
|
support.some((inputValue, index) => {
|
|
|
|
if (inputValue < 0) {
|
|
|
|
inputs[index].setCustomValidity('Poparcie nie może byc mniejsze od 0%');
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
});
|
|
|
|
|
2020-11-08 15:59:50 +00:00
|
|
|
const supportSum = support.reduce((a, b) => a + b, 0);
|
|
|
|
if (supportSum > 100) {
|
2022-08-01 20:53:21 +00:00
|
|
|
inputs[0].setCustomValidity('Suma poparcia nie może przekraczać 100%');
|
|
|
|
} else if (supportSum <= 0) {
|
|
|
|
inputs[0].setCustomValidity('Suma poparcia musi być wyższa niż 0%');
|
2020-11-08 15:59:50 +00:00
|
|
|
}
|
|
|
|
|
2022-08-01 20:53:21 +00:00
|
|
|
return form.reportValidity();
|
2020-11-08 15:59:50 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
export const calculate = (): void => {
|
2022-08-01 20:53:21 +00:00
|
|
|
const form = document.querySelector<HTMLFormElement>('#support-form')!;
|
2020-11-08 15:59:50 +00:00
|
|
|
const inputs = document.querySelectorAll<HTMLInputElement>('#support-form input');
|
|
|
|
const support = Array
|
|
|
|
.from(inputs)
|
|
|
|
.map((input) => parseFloat(input.value))
|
|
|
|
.map((value) => (Number.isNaN(value) ? 0 : value));
|
|
|
|
|
2022-08-01 20:53:21 +00:00
|
|
|
if (!validate(form, inputs, support)) return;
|
2020-11-08 15:59:50 +00:00
|
|
|
|
|
|
|
const mandates = calculateMandates(support);
|
|
|
|
|
|
|
|
displayResults(mandates);
|
|
|
|
displayUrl(support);
|
|
|
|
barChart = displayBarChart(support);
|
|
|
|
pieChart = displayPieChart(mandates);
|
|
|
|
displayConstituencyResults();
|
|
|
|
|
|
|
|
inputs.forEach((input) => input.addEventListener('input', () => {
|
|
|
|
clearResults();
|
|
|
|
}));
|
|
|
|
};
|
|
|
|
|
|
|
|
export const generateTable = (): void => {
|
|
|
|
const form: HTMLElement = document.getElementById('support-form')!;
|
|
|
|
form.insertAdjacentHTML('afterbegin', tableTemplate({
|
|
|
|
committees,
|
|
|
|
}));
|
|
|
|
};
|
|
|
|
|
|
|
|
export const loadResultsFromUrl = (): void => {
|
2020-11-20 15:56:48 +00:00
|
|
|
if (location.search) {
|
|
|
|
const searchParams = new URLSearchParams(location.search);
|
2020-11-08 15:59:50 +00:00
|
|
|
searchParams.forEach((value, key) => {
|
|
|
|
const input = document.querySelector<HTMLInputElement>(`tr.${key} td:nth-child(2) input`);
|
|
|
|
if (input) input.value = value;
|
|
|
|
});
|
|
|
|
calculate();
|
|
|
|
}
|
|
|
|
};
|