|
58 | 58 | </div> |
59 | 59 | </div> |
60 | 60 | </div> |
| 61 | + <div class="row mt-5"> |
| 62 | + <div class="col-md-12"> |
| 63 | + <div class="card border-primary mb-3 ml-3" id="statsPanel"> |
| 64 | + <div class="card-header">Stats</div> |
| 65 | + <div class="card-body"> |
| 66 | + <div class="row"> |
| 67 | + <div class="col-md-4"> |
| 68 | + <div id="recordFormatDist" class="chart-container"></div></div> |
| 69 | + <div class="col-md-4"> |
| 70 | + <div id="mapAuthorChart"></div> |
| 71 | + </div> |
| 72 | + <div class="col-md-4"> |
| 73 | + <div id="submissionTypeChart"></div> |
| 74 | + </div> |
| 75 | + </div> |
| 76 | + </div> |
| 77 | + </div> |
| 78 | + </div> |
| 79 | + </div> |
61 | 80 | <div class="row mt-5"> |
62 | 81 | <div class="col-md-12"> |
63 | 82 | <div class="card border-primary mb-3 ml-3" id="validationSubmissionsPanel"> |
|
539 | 558 | <%- include('../snippets/scripts.ejs'); %> |
540 | 559 | <script type="text/javascript" src="../assets/vendor/Enchanter/enchanter.js"></script> |
541 | 560 | <script type="text/javascript" src="../assets/custom/js/spinWheel.js"></script> |
| 561 | +
|
| 562 | +<script src="https://cdn.amcharts.com/lib/5/index.js"></script> |
| 563 | +<script src="https://cdn.amcharts.com/lib/5/xy.js"></script> |
| 564 | +<script src="https://cdn.amcharts.com/lib/5/themes/Animated.js"></script> |
| 565 | +<script src="https://cdn.amcharts.com/lib/5/locales/de_DE.js"></script> |
| 566 | +<script src="https://cdn.amcharts.com/lib/5/geodata/germanyLow.js"></script> |
| 567 | +<script src="https://cdn.amcharts.com/lib/5/fonts/notosans-sc.js"></script> |
| 568 | +<script src="https://cdn.amcharts.com/lib/5/percent.js"></script> |
| 569 | +<script src="https://cdn.amcharts.com/lib/5/themes/Animated.js"></script> |
| 570 | +
|
| 571 | +
|
542 | 572 | </body> |
543 | 573 | </html> |
544 | 574 |
|
|
1686 | 1716 | } |
1687 | 1717 | }); |
1688 | 1718 | } |
| 1719 | + <% if (indexModel && indexModel.currentActiveRound) { %> |
| 1720 | + ((() => { |
| 1721 | + async function createSubmissionCharts() { |
| 1722 | +
|
| 1723 | + let submissions = []; |
| 1724 | + try { |
| 1725 | + const roundId = <%- indexModel.currentActiveRound.id %>; |
| 1726 | + const result = await fetch(`${baseUrl}/submission/${roundId}/validAndVerifiedSubmissions`); |
| 1727 | + submissions = await result.json(); |
| 1728 | + } catch (e) { |
| 1729 | + console.error(e.message); |
| 1730 | + submissions = []; |
| 1731 | + } |
| 1732 | +
|
| 1733 | + createRecordedFormatChart(submissions); |
| 1734 | + createAuthorDistributionChart(submissions); |
| 1735 | + createSubmissionTypeChart(submissions); |
| 1736 | + } |
| 1737 | +
|
| 1738 | + function createChart(containerId, title, data, colors) { |
| 1739 | + const statsBody = document.querySelector(`#${containerId}`); |
| 1740 | + if (!statsBody) { |
| 1741 | + console.error(`Chart container #${containerId} not found`); |
| 1742 | + return; |
| 1743 | + } |
| 1744 | +
|
| 1745 | + const chartContainer = document.createElement('div'); |
| 1746 | + chartContainer.id = `${containerId}Chart`; |
| 1747 | + chartContainer.style.width = '100%'; |
| 1748 | + chartContainer.style.height = '300px'; |
| 1749 | +
|
| 1750 | + statsBody.innerHTML = `<h6 class="mb-3 text-white text-center">${title}</h6>`; |
| 1751 | + statsBody.appendChild(chartContainer); |
| 1752 | +
|
| 1753 | + const root = am5.Root.new(`${containerId}Chart`); |
| 1754 | + root.setThemes([am5themes_Animated.new(root)]); |
| 1755 | +
|
| 1756 | + const chart = root.container.children.push(am5percent.PieChart.new(root, { |
| 1757 | + layout: root.verticalLayout, |
| 1758 | + innerRadius: am5.percent(50) |
| 1759 | + })); |
| 1760 | +
|
| 1761 | + // Add "No data" message |
| 1762 | + const noDataLabel = chart.children.unshift(am5.Label.new(root, { |
| 1763 | + text: "No chart data available", |
| 1764 | + fontSize: 14, |
| 1765 | + fontWeight: "500", |
| 1766 | + textAlign: "center", |
| 1767 | + x: am5.percent(50), |
| 1768 | + y: am5.percent(50), |
| 1769 | + centerX: am5.percent(50), |
| 1770 | + centerY: am5.percent(50), |
| 1771 | + fill: am5.color("#6c757d") |
| 1772 | + })); |
| 1773 | +
|
| 1774 | + const series = chart.series.push(am5percent.PieSeries.new(root, { |
| 1775 | + valueField: "count", |
| 1776 | + categoryField: "category", |
| 1777 | + alignLabels: false |
| 1778 | + })); |
| 1779 | +
|
| 1780 | + series.get("colors").set("colors", colors.map(color => am5.color(color))); |
| 1781 | +
|
| 1782 | + series.labels.template.setAll({ |
| 1783 | + textType: "circular", |
| 1784 | + centerX: 0, |
| 1785 | + centerY: 0, |
| 1786 | + fontSize: "12px", |
| 1787 | + fontWeight: "500", |
| 1788 | + fill: am5.color("#ffffff"), |
| 1789 | + oversizedBehavior: "wrap" |
| 1790 | + }); |
| 1791 | +
|
| 1792 | + series.slices.template.setAll({ |
| 1793 | + stroke: am5.color("#495057"), |
| 1794 | + strokeWidth: 2, |
| 1795 | + cornerRadius: 3, |
| 1796 | + shadowOpacity: 0.1, |
| 1797 | + shadowOffsetX: 2, |
| 1798 | + shadowOffsetY: 2, |
| 1799 | + shadowBlur: 5, |
| 1800 | + shadowColor: am5.color("#000000"), |
| 1801 | + toggleKey: "active" |
| 1802 | + }); |
| 1803 | +
|
| 1804 | + series.slices.template.states.create("hover", { |
| 1805 | + scale: 1.05, |
| 1806 | + shadowOpacity: 0.3, |
| 1807 | + shadowOffsetX: 3, |
| 1808 | + shadowOffsetY: 3, |
| 1809 | + shadowBlur: 8 |
| 1810 | + }); |
| 1811 | +
|
| 1812 | + series.slices.template.states.create("active", { |
| 1813 | + scale: 1.1, |
| 1814 | + shiftRadius: 15 |
| 1815 | + }); |
| 1816 | +
|
| 1817 | + series.slices.template.set("cursorOverStyle", "pointer"); |
| 1818 | + series.slices.template.set("tooltipText", "{category}: {count} submissions ({valuePercentTotal.formatNumber('#.0')}%)"); |
| 1819 | +
|
| 1820 | + series.set("tooltip", am5.Tooltip.new(root, { |
| 1821 | + getFillFromSprite: false, |
| 1822 | + autoTextColor: false |
| 1823 | + })); |
| 1824 | +
|
| 1825 | + series.get("tooltip").get("background").setAll({ |
| 1826 | + fill: am5.color("#343a40"), |
| 1827 | + stroke: am5.color("#6c757d"), |
| 1828 | + strokeWidth: 1, |
| 1829 | + cornerRadius: 4, |
| 1830 | + fillOpacity: 0.95 |
| 1831 | + }); |
| 1832 | +
|
| 1833 | + series.get("tooltip").label.setAll({ |
| 1834 | + fill: am5.color("#ffffff"), |
| 1835 | + fontSize: "11px", |
| 1836 | + fontWeight: "500" |
| 1837 | + }); |
| 1838 | +
|
| 1839 | + const legend = chart.children.push(am5.Legend.new(root, { |
| 1840 | + centerX: am5.percent(50), |
| 1841 | + x: am5.percent(50), |
| 1842 | + marginTop: 10, |
| 1843 | + marginBottom: 10 |
| 1844 | + })); |
| 1845 | +
|
| 1846 | + legend.labels.template.setAll({ |
| 1847 | + fontSize: "12px", |
| 1848 | + fontWeight: "500", |
| 1849 | + fill: am5.color("#ffffff") |
| 1850 | + }); |
| 1851 | +
|
| 1852 | + legend.valueLabels.template.setAll({ |
| 1853 | + fill: am5.color("#ffffff") |
| 1854 | + }); |
| 1855 | +
|
| 1856 | + legend.markers.template.setAll({ |
| 1857 | + width: 14, |
| 1858 | + height: 14 |
| 1859 | + }); |
| 1860 | +
|
| 1861 | + legend.data.setAll(series.dataItems); |
| 1862 | +
|
| 1863 | + if (!data || data.length === 0) { |
| 1864 | + chart.set("opacity", 0.3); |
| 1865 | + series.set("visible", false); |
| 1866 | + legend.set("visible", false); |
| 1867 | + series.data.setAll([]); |
| 1868 | + } else { |
| 1869 | + noDataLabel.set("visible", false); |
| 1870 | + chart.set("opacity", 1); |
| 1871 | + series.set("visible", true); |
| 1872 | + legend.set("visible", true); |
| 1873 | + series.data.setAll(data); |
| 1874 | + series.appear(1000, 100); |
| 1875 | + } |
| 1876 | +
|
| 1877 | + return chart; |
| 1878 | + } |
| 1879 | +
|
| 1880 | + function createRecordedFormatChart(submissions) { |
| 1881 | + if (!submissions || submissions.length === 0) { |
| 1882 | + createChart('recordFormatDist', 'Recorded Format', [], ['#4CAF50', '#F44336']); |
| 1883 | + return; |
| 1884 | + } |
| 1885 | +
|
| 1886 | + const formatCounts = submissions.reduce((acc, submission) => { |
| 1887 | + const format = submission.recordedFormat || 'Practised'; |
| 1888 | + acc[format] = (acc[format] || 0) + 1; |
| 1889 | + return acc; |
| 1890 | + }, {}); |
| 1891 | +
|
| 1892 | + const chartData = Object.entries(formatCounts).map(([format, count]) => ({ |
| 1893 | + category: format, |
| 1894 | + count: count |
| 1895 | + })); |
| 1896 | +
|
| 1897 | + createChart('recordFormatDist', 'Recorded Format', chartData, ['#4CAF50', '#F44336']); |
| 1898 | + } |
| 1899 | +
|
| 1900 | + function createAuthorDistributionChart(submissions) { |
| 1901 | + if (!submissions || submissions.length === 0) { |
| 1902 | + createChart('mapAuthorChart', 'Map Author', [], ['#FF9800', '#2196F3']); |
| 1903 | + return; |
| 1904 | + } |
| 1905 | +
|
| 1906 | + const authorCounts = submissions.reduce((acc, submission) => { |
| 1907 | + const type = submission.author ? 'Yes' : 'No'; |
| 1908 | + acc[type] = (acc[type] || 0) + 1; |
| 1909 | + return acc; |
| 1910 | + }, {}); |
| 1911 | +
|
| 1912 | + const chartData = Object.entries(authorCounts).map(([type, count]) => ({ |
| 1913 | + category: type, |
| 1914 | + count: count |
| 1915 | + })); |
| 1916 | +
|
| 1917 | + createChart('mapAuthorChart', 'Map Author', chartData, ['#FF9800', '#2196F3']); |
| 1918 | + } |
| 1919 | +
|
| 1920 | + function createSubmissionTypeChart(submissions) { |
| 1921 | + if (!submissions || submissions.length === 0) { |
| 1922 | + createChart('submissionTypeChart', 'Submission Distributable', [], ['#9C27B0', '#607D8B']); |
| 1923 | + return; |
| 1924 | + } |
| 1925 | +
|
| 1926 | + const typeCounts = submissions.reduce((acc, submission) => { |
| 1927 | + const type = !(submission.author && !submission.distributable) ? 'Yes' : 'No'; |
| 1928 | + acc[type] = (acc[type] || 0) + 1; |
| 1929 | + return acc; |
| 1930 | + }, {}); |
| 1931 | +
|
| 1932 | + const chartData = Object.entries(typeCounts).map(([type, count]) => ({ |
| 1933 | + category: type, |
| 1934 | + count: count |
| 1935 | + })); |
| 1936 | +
|
| 1937 | + createChart('submissionTypeChart', 'Submission Distributable', chartData, ['#9C27B0', '#607D8B']); |
| 1938 | + } |
| 1939 | +
|
| 1940 | + createSubmissionCharts() |
| 1941 | + })()) |
| 1942 | + <% } %> |
1689 | 1943 | }); |
1690 | 1944 | </script> |
0 commit comments