|
4 | 4 | <meta charset="UTF-8"> |
5 | 5 | <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
6 | 6 | <title>IS Research Streams Explorer | 2000-2025</title> |
| 7 | + <!-- Chart.js for interactive charts --> |
| 8 | + <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script> |
7 | 9 | <style> |
8 | 10 | * { |
9 | 11 | margin: 0; |
@@ -439,9 +441,10 @@ <h1>🔬 IS Research Streams Explorer</h1> |
439 | 441 | <button class="tab active" onclick="showPanel('overview')">📊 Overview</button> |
440 | 442 | <button class="tab" onclick="showPanel('explorer')">🔍 Explorer</button> |
441 | 443 | <button class="tab" onclick="showPanel('streams')">🌊 Research Streams</button> |
| 444 | + <button class="tab" onclick="showPanel('trends')">📈 Trends</button> |
442 | 445 | <button class="tab" onclick="showPanel('emerging')">🔥 Emerging Topics</button> |
443 | 446 | <button class="tab" onclick="showPanel('impactful')">⭐ Most Impactful</button> |
444 | | - <button class="tab" onclick="showPanel('visualizations')">📈 Visualizations</button> |
| 447 | + <button class="tab" onclick="showPanel('visualizations')">� Visualizations</button> |
445 | 448 | <button class="tab" onclick="showPanel('top-papers')">📚 Top Papers</button> |
446 | 449 | <button class="tab" onclick="showPanel('about')">ℹ️ About</button> |
447 | 450 | </div> |
@@ -529,6 +532,55 @@ <h2 style="margin-bottom: 10px;">🔍 Hierarchical Literature Explorer</h2> |
529 | 532 | </div> |
530 | 533 | </div> |
531 | 534 |
|
| 535 | + <!-- Trends Panel --> |
| 536 | + <div id="trends" class="panel"> |
| 537 | + <div class="viz-container"> |
| 538 | + <h2 style="margin-bottom: 20px;">📈 Temporal Evolution & Trends</h2> |
| 539 | + <p style="color: #64748b; margin-bottom: 30px;">Explore how research streams have evolved over nearly 50 years of IS research</p> |
| 540 | + |
| 541 | + <!-- Stream Timeline Chart --> |
| 542 | + <div style="margin-bottom: 40px;"> |
| 543 | + <h3 style="color: var(--primary); margin-bottom: 15px;">📊 Research Stream Evolution (1977-2026)</h3> |
| 544 | + <p style="color: #64748b; font-size: 0.9rem; margin-bottom: 20px;">Papers published per year by research stream</p> |
| 545 | + <canvas id="streamTimelineChart" style="max-height: 400px;"></canvas> |
| 546 | + </div> |
| 547 | + |
| 548 | + <!-- Cumulative Growth Chart --> |
| 549 | + <div style="margin-bottom: 40px;"> |
| 550 | + <h3 style="color: var(--primary); margin-bottom: 15px;">📈 Cumulative Growth</h3> |
| 551 | + <p style="color: #64748b; font-size: 0.9rem; margin-bottom: 20px;">Total papers published over time across all journals</p> |
| 552 | + <canvas id="cumulativeGrowthChart" style="max-height: 300px;"></canvas> |
| 553 | + </div> |
| 554 | + |
| 555 | + <!-- Journal Distribution by Decade --> |
| 556 | + <div style="margin-bottom: 40px;"> |
| 557 | + <h3 style="color: var(--primary); margin-bottom: 15px;">📚 Journal Contribution by Decade</h3> |
| 558 | + <p style="color: #64748b; font-size: 0.9rem; margin-bottom: 20px;">Publication distribution across the AIS Basket of Eight</p> |
| 559 | + <canvas id="journalDecadeChart" style="max-height: 400px;"></canvas> |
| 560 | + </div> |
| 561 | + |
| 562 | + <!-- Emerging Topics Bubble Chart --> |
| 563 | + <div style="margin-bottom: 40px;"> |
| 564 | + <h3 style="color: var(--primary); margin-bottom: 15px;">🔥 Emerging vs. Established Topics</h3> |
| 565 | + <p style="color: #64748b; font-size: 0.9rem; margin-bottom: 20px;"> |
| 566 | + Research streams by average publication year and size (bubble size = paper count) |
| 567 | + </p> |
| 568 | + <canvas id="emergingTopicsBubbleChart" style="max-height: 500px;"></canvas> |
| 569 | + </div> |
| 570 | + |
| 571 | + <!-- Stream Activity Heatmap --> |
| 572 | + <div style="margin-bottom: 20px;"> |
| 573 | + <h3 style="color: var(--primary); margin-bottom: 15px;">🔥 Research Activity Heatmap</h3> |
| 574 | + <p style="color: #64748b; font-size: 0.9rem; margin-bottom: 20px;"> |
| 575 | + Publication intensity by stream and 5-year period |
| 576 | + </p> |
| 577 | + <div id="activityHeatmap" style="overflow-x: auto;"> |
| 578 | + <!-- Will be populated by JavaScript --> |
| 579 | + </div> |
| 580 | + </div> |
| 581 | + </div> |
| 582 | + </div> |
| 583 | + |
532 | 584 | <!-- Research Streams Panel --> |
533 | 585 | <div id="streams" class="panel"> |
534 | 586 | <div class="controls"> |
@@ -867,6 +919,8 @@ <h3 style="color: var(--primary); margin-bottom: 15px;">🙏 Acknowledgments</h3 |
867 | 919 | loadExplorer(); |
868 | 920 | } else if (panelId === 'streams' && !document.getElementById('streamsContainer').dataset.loaded) { |
869 | 921 | loadAllStreams(); |
| 922 | + } else if (panelId === 'trends') { |
| 923 | + initializeTrendsCharts(); |
870 | 924 | } else if (panelId === 'emerging') { |
871 | 925 | loadEmergingStreams(); |
872 | 926 | } else if (panelId === 'impactful') { |
@@ -1602,6 +1656,262 @@ <h4 style="margin-top: 0; margin-bottom: 15px; color: var(--primary);">📊 Filt |
1602 | 1656 | document.body.removeChild(a); |
1603 | 1657 | URL.revokeObjectURL(url); |
1604 | 1658 | } |
| 1659 | + |
| 1660 | + // ===== TRENDS VISUALIZATION FUNCTIONS ===== |
| 1661 | + |
| 1662 | + let trendsChartsInitialized = false; |
| 1663 | + let streamTimelineChart, cumulativeGrowthChart, journalDecadeChart, emergingTopicsBubbleChart; |
| 1664 | + |
| 1665 | + function initializeTrendsCharts() { |
| 1666 | + if (trendsChartsInitialized) return; |
| 1667 | + |
| 1668 | + console.log('Initializing trends charts...'); |
| 1669 | + |
| 1670 | + // Prepare data from dashboardData |
| 1671 | + const papers = dashboardData.papers; |
| 1672 | + const streams = dashboardData.streams.filter(s => s.id >= 0); // Exclude unclassified |
| 1673 | + |
| 1674 | + // 1. Stream Timeline Data (papers per year by stream) |
| 1675 | + const yearlyStreamData = {}; |
| 1676 | + papers.forEach(paper => { |
| 1677 | + if (paper.l1 >= 0) { |
| 1678 | + const year = paper.year; |
| 1679 | + if (!yearlyStreamData[year]) yearlyStreamData[year] = {}; |
| 1680 | + if (!yearlyStreamData[year][paper.l1]) yearlyStreamData[year][paper.l1] = 0; |
| 1681 | + yearlyStreamData[year][paper.l1]++; |
| 1682 | + } |
| 1683 | + }); |
| 1684 | + |
| 1685 | + createStreamTimelineChart(yearlyStreamData, streams); |
| 1686 | + createCumulativeGrowthChart(papers); |
| 1687 | + createJournalDecadeChart(papers); |
| 1688 | + createEmergingTopicsBubbleChart(streams); |
| 1689 | + createActivityHeatmap(yearlyStreamData, streams); |
| 1690 | + |
| 1691 | + trendsChartsInitialized = true; |
| 1692 | + } |
| 1693 | + |
| 1694 | + function createStreamTimelineChart(yearlyData, streams) { |
| 1695 | + const ctx = document.getElementById('streamTimelineChart'); |
| 1696 | + if (!ctx) return; |
| 1697 | + |
| 1698 | + const years = Object.keys(yearlyData).map(Number).sort((a, b) => a - b); |
| 1699 | + const streamColors = [ |
| 1700 | + '#2563eb', '#7c3aed', '#db2777', '#059669', |
| 1701 | + '#d97706', '#dc2626', '#0891b2', '#4f46e5' |
| 1702 | + ]; |
| 1703 | + |
| 1704 | + const datasets = streams.map((stream, idx) => ({ |
| 1705 | + label: stream.title, |
| 1706 | + data: years.map(year => yearlyData[year]?.[stream.id] || 0), |
| 1707 | + borderColor: streamColors[idx % streamColors.length], |
| 1708 | + backgroundColor: streamColors[idx % streamColors.length] + '20', |
| 1709 | + fill: false, |
| 1710 | + tension: 0.3 |
| 1711 | + })); |
| 1712 | + |
| 1713 | + streamTimelineChart = new Chart(ctx, { |
| 1714 | + type: 'line', |
| 1715 | + data: { labels: years, datasets: datasets }, |
| 1716 | + options: { |
| 1717 | + responsive: true, |
| 1718 | + maintainAspectRatio: true, |
| 1719 | + plugins: { |
| 1720 | + legend: { position: 'bottom', labels: { boxWidth: 12, font: { size: 10 } } }, |
| 1721 | + tooltip: { mode: 'index', intersect: false } |
| 1722 | + }, |
| 1723 | + scales: { |
| 1724 | + y: { beginAtZero: true, title: { display: true, text: 'Papers per Year' } }, |
| 1725 | + x: { title: { display: true, text: 'Year' } } |
| 1726 | + } |
| 1727 | + } |
| 1728 | + }); |
| 1729 | + } |
| 1730 | + |
| 1731 | + function createCumulativeGrowthChart(papers) { |
| 1732 | + const ctx = document.getElementById('cumulativeGrowthChart'); |
| 1733 | + if (!ctx) return; |
| 1734 | + |
| 1735 | + const yearCounts = {}; |
| 1736 | + papers.forEach(p => { |
| 1737 | + const year = p.year; |
| 1738 | + if (year) yearCounts[year] = (yearCounts[year] || 0) + 1; |
| 1739 | + }); |
| 1740 | + |
| 1741 | + const years = Object.keys(yearCounts).map(Number).sort((a, b) => a - b); |
| 1742 | + let cumulative = 0; |
| 1743 | + const cumulativeData = years.map(year => { |
| 1744 | + cumulative += yearCounts[year] || 0; |
| 1745 | + return cumulative; |
| 1746 | + }); |
| 1747 | + |
| 1748 | + cumulativeGrowthChart = new Chart(ctx, { |
| 1749 | + type: 'line', |
| 1750 | + data: { |
| 1751 | + labels: years, |
| 1752 | + datasets: [{ |
| 1753 | + label: 'Total Papers', |
| 1754 | + data: cumulativeData, |
| 1755 | + borderColor: '#2563eb', |
| 1756 | + backgroundColor: 'rgba(37, 99, 235, 0.1)', |
| 1757 | + fill: true, |
| 1758 | + tension: 0.3 |
| 1759 | + }] |
| 1760 | + }, |
| 1761 | + options: { |
| 1762 | + responsive: true, |
| 1763 | + maintainAspectRatio: true, |
| 1764 | + plugins: { |
| 1765 | + legend: { display: false }, |
| 1766 | + tooltip: { callbacks: { label: ctx => `Total: ${ctx.parsed.y.toLocaleString()} papers` } } |
| 1767 | + }, |
| 1768 | + scales: { |
| 1769 | + y: { beginAtZero: true, title: { display: true, text: 'Cumulative Papers' } }, |
| 1770 | + x: { title: { display: true, text: 'Year' } } |
| 1771 | + } |
| 1772 | + } |
| 1773 | + }); |
| 1774 | + } |
| 1775 | + |
| 1776 | + function createJournalDecadeChart(papers) { |
| 1777 | + const ctx = document.getElementById('journalDecadeChart'); |
| 1778 | + if (!ctx) return; |
| 1779 | + |
| 1780 | + const journals = [...new Set(papers.map(p => p.journal))].sort(); |
| 1781 | + const decades = ['1977-1989', '1990-1999', '2000-2009', '2010-2019', '2020-2026']; |
| 1782 | + |
| 1783 | + const decadeData = decades.map(() => ({})); |
| 1784 | + papers.forEach(p => { |
| 1785 | + const year = p.year; |
| 1786 | + let decadeIdx = -1; |
| 1787 | + if (year >= 1977 && year <= 1989) decadeIdx = 0; |
| 1788 | + else if (year >= 1990 && year <= 1999) decadeIdx = 1; |
| 1789 | + else if (year >= 2000 && year <= 2009) decadeIdx = 2; |
| 1790 | + else if (year >= 2010 && year <= 2019) decadeIdx = 3; |
| 1791 | + else if (year >= 2020 && year <= 2026) decadeIdx = 4; |
| 1792 | + |
| 1793 | + if (decadeIdx >= 0) { |
| 1794 | + if (!decadeData[decadeIdx][p.journal]) decadeData[decadeIdx][p.journal] = 0; |
| 1795 | + decadeData[decadeIdx][p.journal]++; |
| 1796 | + } |
| 1797 | + }); |
| 1798 | + |
| 1799 | + const journalColors = [ |
| 1800 | + '#2563eb', '#7c3aed', '#db2777', '#059669', |
| 1801 | + '#d97706', '#dc2626', '#0891b2', '#4f46e5' |
| 1802 | + ]; |
| 1803 | + |
| 1804 | + const datasets = journals.map((journal, idx) => ({ |
| 1805 | + label: journal, |
| 1806 | + data: decades.map((_, di) => decadeData[di][journal] || 0), |
| 1807 | + backgroundColor: journalColors[idx % journalColors.length] |
| 1808 | + })); |
| 1809 | + |
| 1810 | + journalDecadeChart = new Chart(ctx, { |
| 1811 | + type: 'bar', |
| 1812 | + data: { labels: decades, datasets: datasets }, |
| 1813 | + options: { |
| 1814 | + responsive: true, |
| 1815 | + maintainAspectRatio: true, |
| 1816 | + plugins: { |
| 1817 | + legend: { position: 'bottom', labels: { boxWidth: 12, font: { size: 10 } } } |
| 1818 | + }, |
| 1819 | + scales: { |
| 1820 | + x: { stacked: true, title: { display: true, text: 'Decade' } }, |
| 1821 | + y: { stacked: true, beginAtZero: true, title: { display: true, text: 'Papers' } } |
| 1822 | + } |
| 1823 | + } |
| 1824 | + }); |
| 1825 | + } |
| 1826 | + |
| 1827 | + function createEmergingTopicsBubbleChart(streams) { |
| 1828 | + const ctx = document.getElementById('emergingTopicsBubbleChart'); |
| 1829 | + if (!ctx) return; |
| 1830 | + |
| 1831 | + const data = streams.map(stream => ({ |
| 1832 | + x: stream.avgYear || 2000, |
| 1833 | + y: stream.avgCitations || 0, |
| 1834 | + r: Math.sqrt(stream.size) / 3, // Bubble size proportional to paper count |
| 1835 | + label: stream.title, |
| 1836 | + papers: stream.size |
| 1837 | + })); |
| 1838 | + |
| 1839 | + emergingTopicsBubbleChart = new Chart(ctx, { |
| 1840 | + type: 'bubble', |
| 1841 | + data: { |
| 1842 | + datasets: [{ |
| 1843 | + label: 'Research Streams', |
| 1844 | + data: data, |
| 1845 | + backgroundColor: 'rgba(37, 99, 235, 0.6)', |
| 1846 | + borderColor: '#2563eb', |
| 1847 | + borderWidth: 1 |
| 1848 | + }] |
| 1849 | + }, |
| 1850 | + options: { |
| 1851 | + responsive: true, |
| 1852 | + maintainAspectRatio: true, |
| 1853 | + plugins: { |
| 1854 | + legend: { display: false }, |
| 1855 | + tooltip: { |
| 1856 | + callbacks: { |
| 1857 | + label: ctx => { |
| 1858 | + const item = ctx.raw; |
| 1859 | + return [ |
| 1860 | + item.label, |
| 1861 | + `Avg Year: ${item.x.toFixed(1)}`, |
| 1862 | + `Avg Citations: ${item.y.toFixed(1)}`, |
| 1863 | + `Papers: ${item.papers}` |
| 1864 | + ]; |
| 1865 | + } |
| 1866 | + } |
| 1867 | + } |
| 1868 | + }, |
| 1869 | + scales: { |
| 1870 | + x: { title: { display: true, text: 'Average Publication Year' }, min: 1990, max: 2025 }, |
| 1871 | + y: { title: { display: true, text: 'Average Citations' }, beginAtZero: true } |
| 1872 | + } |
| 1873 | + } |
| 1874 | + }); |
| 1875 | + } |
| 1876 | + |
| 1877 | + function createActivityHeatmap(yearlyData, streams) { |
| 1878 | + const container = document.getElementById('activityHeatmap'); |
| 1879 | + if (!container) return; |
| 1880 | + |
| 1881 | + const periods = ['1977-1984', '1985-1994', '1995-2004', '2005-2014', '2015-2026']; |
| 1882 | + |
| 1883 | + const periodData = streams.map(stream => { |
| 1884 | + return periods.map((_, pidx) => { |
| 1885 | + const startYear = 1977 + (pidx * 10) - (pidx === 0 ? 0 : 2); |
| 1886 | + const endYear = pidx === periods.length - 1 ? 2026 : startYear + 9; |
| 1887 | + let count = 0; |
| 1888 | + for (let year = startYear; year <= endYear; year++) { |
| 1889 | + count += (yearlyData[year]?.[stream.id] || 0); |
| 1890 | + } |
| 1891 | + return count; |
| 1892 | + }); |
| 1893 | + }); |
| 1894 | + |
| 1895 | + const maxCount = Math.max(...periodData.flat()); |
| 1896 | + |
| 1897 | + let html = '<table style="width: 100%; border-collapse: collapse; font-size: 0.85rem;">'; |
| 1898 | + html += '<tr><th style="text-align: left; padding: 8px; border: 1px solid #e2e8f0;">Stream</th>'; |
| 1899 | + periods.forEach(p => html += `<th style="padding: 8px; border: 1px solid #e2e8f0;">${p}</th>`); |
| 1900 | + html += '</tr>'; |
| 1901 | + |
| 1902 | + streams.forEach((stream, sidx) => { |
| 1903 | + html += `<tr><td style="padding: 8px; border: 1px solid #e2e8f0; font-weight: 500;">${stream.title}</td>`; |
| 1904 | + periodData[sidx].forEach(count => { |
| 1905 | + const intensity = maxCount > 0 ? count / maxCount : 0; |
| 1906 | + const color = `rgba(37, 99, 235, ${0.1 + intensity * 0.9})`; |
| 1907 | + html += `<td style="padding: 8px; border: 1px solid #e2e8f0; background: ${color}; text-align: center;">${count}</td>`; |
| 1908 | + }); |
| 1909 | + html += '</tr>'; |
| 1910 | + }); |
| 1911 | + html += '</table>'; |
| 1912 | + |
| 1913 | + container.innerHTML = html; |
| 1914 | + } |
1605 | 1915 |
|
1606 | 1916 | // Initialize |
1607 | 1917 | document.addEventListener('DOMContentLoaded', function() { |
|
0 commit comments