瀏覽代碼

Retain retrieved files, add permalink

= 10 月之前
父節點
當前提交
e7a3751667

+ 7 - 44
index.html

@@ -1,50 +1,13 @@
 <!DOCTYPE html>
 <html lang="en">
 <head>
-    <meta charset="UTF-8">
-    <meta name="viewport" content="width=device-width, initial-scale=1.0">
-    <title>PrUn Financial Reports</title>
-	<link rel="stylesheet" href="styles.css">
-	<link rel="icon" type="image/x-icon" href="icon128.png">
-	<script src="material-info.js"></script>
-	<script src="main.js"></script>
-	<script src="https://cdn.plot.ly/plotly-3.0.1.min.js" charset="utf-8"></script>
+  <meta charset="UTF-8">
+  <title>Redirecting...</title>
+  <script>
+    window.location.href = "https://pmmg-products.github.io/reports/";
+  </script>
 </head>
 <body>
-
-<div class="topTabContainer">
-	<div class="topTab">
-		<a class="topTabLink">Stats</a>
-		<div class="toggleIndicator toggleIndicatorActive"></div>
-	</div>
-	<div class="topTab">
-		<a class="topTabLink" href="https://github.com/PMMG-Products/pmmg-products.github.io">GitHub</a>
-		<div class="toggleIndicator"></div>
-	</div>
-</div>
-
-<div class="mainContainer">
-	<div class="plotSelectorContainer" id="graphTypeContainer">
-		<label>
-			Graph: 
-			<select class="plotSelector" id="graphType">
-				<option value="topProduction">Top Production</option>
-				<option value="topCompanies">Top Companies</option>
-				<option value="matHistory">MAT History</option>
-				<option value="compTotals">Company Totals</option>
-				<option value="compHistory">Company History</option>
-				<option value="compRank">Company Rank</option>
-				
-			</select>
-		</label>
-	</div>
-	<div class="selectorSubtypes" id="selectorSubtypes">
-
-	</div>
-	<div class="mainPlot" id="mainPlotContainer">
-		<div id="mainPlot"></div>
-	</div>
-</div>
-
+  <p>If you're not redirected, <a href="https://pmmg-products.github.io/reports">click here</a>.</p>
 </body>
-</html>
+</html>

+ 0 - 0
LICENSE → reports/LICENSE


+ 0 - 0
data/company-data-apr25.csv → reports/data/company-data-apr25.csv


+ 0 - 0
data/company-data-apr25.json → reports/data/company-data-apr25.json


+ 0 - 0
data/company-data-jun25.csv → reports/data/company-data-jun25.csv


+ 0 - 0
data/company-data-jun25.json → reports/data/company-data-jun25.json


+ 0 - 0
data/company-data-mar25.csv → reports/data/company-data-mar25.csv


+ 0 - 0
data/company-data-mar25.json → reports/data/company-data-mar25.json


+ 0 - 0
data/company-data-may25.csv → reports/data/company-data-may25.csv


+ 0 - 0
data/company-data-may25.json → reports/data/company-data-may25.json


文件差異過大導致無法顯示
+ 0 - 0
reports/data/knownCompanies2.json


+ 0 - 0
data/prod-data-apr25.csv → reports/data/prod-data-apr25.csv


+ 0 - 0
data/prod-data-apr25.json → reports/data/prod-data-apr25.json


+ 0 - 0
data/prod-data-jun25.csv → reports/data/prod-data-jun25.csv


+ 0 - 0
data/prod-data-jun25.json → reports/data/prod-data-jun25.json


+ 0 - 0
data/prod-data-mar25.csv → reports/data/prod-data-mar25.csv


+ 0 - 0
data/prod-data-mar25.json → reports/data/prod-data-mar25.json


+ 0 - 0
data/prod-data-may25.csv → reports/data/prod-data-may25.csv


+ 0 - 0
data/prod-data-may25.json → reports/data/prod-data-may25.json


+ 0 - 0
data/rawData/PrUN-LEAD-Data-April-2024.csv → reports/data/rawData/PrUN-LEAD-Data-April-2024.csv


+ 0 - 0
data/rawData/PrUN-LEAD-Data-April-2025.csv → reports/data/rawData/PrUN-LEAD-Data-April-2025.csv


+ 0 - 0
data/rawData/PrUN-LEAD-Data-February-2024.csv → reports/data/rawData/PrUN-LEAD-Data-February-2024.csv


+ 0 - 0
data/rawData/PrUN-LEAD-Data-January-2024.csv → reports/data/rawData/PrUN-LEAD-Data-January-2024.csv


+ 0 - 0
data/rawData/PrUN-LEAD-Data-June-2025.csv → reports/data/rawData/PrUN-LEAD-Data-June-2025.csv


+ 0 - 0
data/rawData/PrUN-LEAD-Data-March-2024.csv → reports/data/rawData/PrUN-LEAD-Data-March-2024.csv


+ 0 - 0
data/rawData/PrUN-LEAD-Data-March-2025.csv → reports/data/rawData/PrUN-LEAD-Data-March-2025.csv


+ 0 - 0
data/rawData/PrUN-LEAD-Data-May-2025.csv → reports/data/rawData/PrUN-LEAD-Data-May-2025.csv


+ 0 - 0
icon128.png → reports/icon128.png


+ 71 - 0
reports/index.html

@@ -0,0 +1,71 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>PrUn Financial Reports</title>
+	<link rel="stylesheet" href="styles.css">
+	<link rel="icon" type="image/x-icon" href="icon128.png">
+	<script src="material-info.js"></script>
+	<script src="main.js"></script>
+	<script src="https://cdn.plot.ly/plotly-3.0.1.min.js" charset="utf-8"></script>
+</head>
+<body>
+
+<div class="topTabContainer" id="topTabContainer">
+	<div>
+		<div class="topTab">
+			<a class="topTabLink" href="https://github.com/PMMG-Products/pmmg-products.github.io">GitHub</a>
+			<div class="toggleIndicator toggleIndicatorActive"></div>
+		</div>
+	</div>
+	<div>
+		<div class="topTab" id="permalinkButton">
+			<a class="topTabLink">Get Permalink</a>
+			<div class="toggleIndicator toggleIndicatorActive"></div>
+		</div>
+		<div class="permalinkContainer" id="permalinkContainer" style="display: none">
+				<div class="permalinkCaret"></div>
+				<div class="permalinkInner">
+					<div style="margin-bottom: 6px">
+						<input class="permalink" id="permalink"></input>
+						<button id="permalinkCopyButton">🔗</button>
+					</div>
+					<div>
+						<input type="checkbox" id="hideOptions"></input>
+						<div>Hide Options?</div>
+					</div>
+					<div>
+						<input type="checkbox" id="latestMonth" checked=true></input>
+						<div>Show Latest Month?</div>
+					</div>
+				</div>
+		</div>
+	</div>
+</div>
+
+<div class="mainContainer">
+	<div class="plotSelectorContainer" id="graphTypeContainer">
+		<label>
+			Graph: 
+			<select class="plotSelector" id="graphType">
+				<option value="topProduction">Top Production</option>
+				<option value="topCompanies">Top Companies</option>
+				<option value="matHistory">MAT History</option>
+				<option value="compTotals">Company Totals</option>
+				<option value="compHistory">Company History</option>
+				<option value="compRank">Company Rank</option>
+				
+			</select>
+		</label>
+	</div>
+	<div class="selectorSubtypes" id="selectorSubtypes">
+
+	</div>
+	<div class="mainPlot" id="mainPlotContainer">
+		<div id="mainPlot"></div>
+	</div>
+</div>
+
+</body>
+</html>

+ 222 - 95
main.js → reports/main.js

@@ -1,3 +1,5 @@
+const loadedData = {};	// Data loaded from files
+
 window.onload = function() {
 	const graphTypeSelector = document.getElementById("graphType");
 	const selectorSubtypes = document.getElementById("selectorSubtypes");
@@ -7,48 +9,106 @@ window.onload = function() {
 		switchPlot();
 	});
 	
-	updateSelectors(graphTypeSelector, selectorSubtypes);
+	updateSelectors(graphTypeSelector, selectorSubtypes, (new URLSearchParams(window.location.search)).has('type'));
 	switchPlot();
+	
+	// Permalink stuff
+	const permalinkContainer = document.getElementById("permalinkContainer");
+	const permalinkButton = document.getElementById("permalinkButton");
+	const permalinkCopyButton = document.getElementById("permalinkCopyButton");
+	const permalinkOptionsButton = document.getElementById("hideOptions");
+	const permalinkLatestMonth = document.getElementById("latestMonth");
+	
+	permalinkButton.addEventListener("click", function(e) {
+		e.stopPropagation();
+		const currentDisplay = permalinkContainer.style.display;
+		if(currentDisplay == "none")
+		{
+			permalinkContainer.style.display = "block";
+		}
+		else
+		{
+			permalinkContainer.style.display = "none";
+		}
+	});
+
+	document.addEventListener("click", function(e) {
+		if(!permalinkContainer.contains(e.target) && !permalinkButton.contains(e.target))
+		{
+			permalinkContainer.style.display = "none";
+		}
+	});
+
+	permalinkCopyButton.addEventListener("click", function() {
+		const permalinkElem = document.getElementById("permalink");
+		if(permalinkElem.value && permalinkElem.value != "")
+		{
+			navigator.clipboard.writeText(permalinkElem.value);
+		}
+	});
+
+	permalinkOptionsButton.addEventListener("change", function() {
+		updatePermalink(graphTypeSelector);
+	});
+	permalinkLatestMonth.addEventListener("change", function() {
+		updatePermalink(graphTypeSelector);
+	});
 };
 
 // Update selectors based on graph type
-function updateSelectors(graphTypeSelector, selectorSubtypes)
+function updateSelectors(graphTypeSelector, selectorSubtypes, useURLParams)
 {
 	const monthsPretty = ["March 3025", "April 3025", "May 3025", "June 3025"];
 	const months = ["mar25", "apr25", "may25", "jun25"];
 	const currentMonth = "jun25";
 	clearChildren(selectorSubtypes);
 	
+	const urlParams = new URLSearchParams(window.location.search);
+	
+	if(urlParams.has('hideOptions'))
+	{
+		const graphTypeContainer = document.getElementById('graphTypeContainer');
+		const topTabs = document.getElementById('topTabContainer');
+		topTabContainer.style.display = 'none';
+		graphTypeContainer.style.display = 'none';
+		selectorSubtypes.style.display = 'none';
+	}
+	
+	if(useURLParams)
+	{
+		graphTypeSelector.value = urlParams.get('type');
+	}
+	
 	if(graphTypeSelector.value == "topProduction")
 	{
-		selectorSubtypes.appendChild(addInput('select', 'metric', 'Metric: ', [['Volume', 'Profit', 'Deficit'], ['volume', 'profit', 'deficit']]));
+		selectorSubtypes.appendChild(addInput('select', 'metric', 'Metric: ', [['Volume', 'Profit', 'Deficit'], ['volume', 'profit', 'deficit']], useURLParams && urlParams.get('metric')));
 		
-		selectorSubtypes.appendChild(addInput('select', 'month', 'Month: ', [monthsPretty, months], currentMonth));
+		selectorSubtypes.appendChild(addInput('select', 'month', 'Month: ', [monthsPretty, months], useURLParams && urlParams.has('month') ? urlParams.get('month') : currentMonth));
 	}
 	else if(graphTypeSelector.value == "topCompanies")
 	{
-		selectorSubtypes.appendChild(addInput('select', 'metric', 'Metric: ', [['Volume', 'Profit'], ['volume', 'profit']]));
+		selectorSubtypes.appendChild(addInput('select', 'metric', 'Metric: ', [['Volume', 'Profit'], ['volume', 'profit']], useURLParams && urlParams.get('metric')));
 		
-		selectorSubtypes.appendChild(addInput('select', 'month', 'Month: ', [monthsPretty, months], currentMonth));
+		selectorSubtypes.appendChild(addInput('select', 'month', 'Month: ', [monthsPretty, months], useURLParams && urlParams.has('month') ? urlParams.get('month') : currentMonth));
 	}
 	else if(graphTypeSelector.value == "matHistory")
 	{
-		selectorSubtypes.appendChild(addInput('select', 'metric', 'Metric: ', [['Volume', 'Profit', 'Price', 'Produced', 'Consumption', 'Surplus'], ['volume', 'profit', 'price', 'amount', 'consumed', 'surplus']]));
+		selectorSubtypes.appendChild(addInput('select', 'metric', 'Metric: ', [['Volume', 'Profit', 'Price', 'Produced', 'Consumption', 'Surplus'], ['volume', 'profit', 'price', 'amount', 'consumed', 'surplus']], useURLParams && urlParams.get('metric')));
 		
-		selectorSubtypes.appendChild(addInput('input', 'mat', 'Ticker: '));
+		selectorSubtypes.appendChild(addInput('input', 'ticker', 'Ticker: ', undefined, useURLParams && urlParams.get('ticker')));
 	}
 	else if(graphTypeSelector.value == "compTotals")
 	{
-		const chartTypeElem = addInput('select', 'chartType', 'Chart Type: ', [['Bar', 'Pie', 'Treemap (Mat)', 'Treemap (Cat)'], ['bar', 'pie', 'treemap', 'treemap-categories']]);
+		const chartTypeElem = addInput('select', 'chartType', 'Chart Type: ', [['Bar', 'Pie', 'Treemap (Mat)', 'Treemap (Cat)'], ['bar', 'pie', 'treemap', 'treemap-categories']], useURLParams && urlParams.get('chartType'));
 		chartTypeElem.style.marginLeft = "-30px";
 		selectorSubtypes.appendChild(chartTypeElem);
 		
-		selectorSubtypes.appendChild(addInput('select', 'metric', 'Metric: ', [['Volume', 'Profit'], ['volume', 'profit']]));
+		selectorSubtypes.appendChild(addInput('select', 'metric', 'Metric: ', [['Volume', 'Profit'], ['volume', 'profit']], useURLParams && urlParams.get('metric')));
 		
-		selectorSubtypes.appendChild(addInput('select', 'month', 'Month: ', [monthsPretty, months], currentMonth));
+		selectorSubtypes.appendChild(addInput('select', 'month', 'Month: ', [monthsPretty, months], useURLParams && urlParams.has('month') ? urlParams.get('month') : currentMonth));
 		
 		// Username input and query button
-		const usernameInput = addInput('input', 'username', 'Username: ');
+		const usernameInput = addInput('input', 'username', 'Username: ', undefined, useURLParams && urlParams.get('username'));
 		
 		const submitButton = document.createElement("button");
 		submitButton.textContent = "Query";
@@ -62,12 +122,12 @@ function updateSelectors(graphTypeSelector, selectorSubtypes)
 		selectorSubtypes.appendChild(usernameInput);
 		
 		// Hidden company ID input, autofilled by query
-		const companyIDInput = addInput('input', 'companyID', 'Company ID: ');
+		const companyIDInput = addInput('input', 'companyID', 'Company ID: ', undefined, useURLParams && urlParams.get('companyID'));
 		companyIDInput.style.visibility = 'hidden';
 		
 		selectorSubtypes.appendChild(companyIDInput);
 		
-		const companyNameInput = addInput('input', 'companyName', 'Company Name: ');
+		const companyNameInput = addInput('input', 'companyName', 'Company Name: ', undefined, useURLParams && urlParams.get('companyName'));
 		companyNameInput.style.visibility = 'hidden';
 		
 		selectorSubtypes.appendChild(companyNameInput);
@@ -75,10 +135,10 @@ function updateSelectors(graphTypeSelector, selectorSubtypes)
 	}
 	else if(graphTypeSelector.value == "compHistory")
 	{
-		selectorSubtypes.appendChild(addInput('select', 'metric', 'Metric: ', [['Volume', 'Profit'], ['volume', 'profit']]));
+		selectorSubtypes.appendChild(addInput('select', 'metric', 'Metric: ', [['Volume', 'Profit'], ['volume', 'profit']], useURLParams && urlParams.get('metric')));
 		
 		// Username input and query button
-		const usernameInput = addInput('input', 'username', 'Username: ');
+		const usernameInput = addInput('input', 'username', 'Username: ', undefined, useURLParams && urlParams.get('username'));
 		
 		const submitButton = document.createElement("button");
 		submitButton.textContent = "Query";
@@ -92,22 +152,22 @@ function updateSelectors(graphTypeSelector, selectorSubtypes)
 		selectorSubtypes.appendChild(usernameInput);
 		
 		// Hidden company ID input, autofilled by query
-		const companyIDInput = addInput('input', 'companyID', 'Company ID: ');
+		const companyIDInput = addInput('input', 'companyID', 'Company ID: ', undefined, useURLParams && urlParams.get('companyID'));
 		companyIDInput.style.visibility = 'hidden';
 		
 		selectorSubtypes.appendChild(companyIDInput);
 		
-		const companyNameInput = addInput('input', 'companyName', 'Company Name: ');
+		const companyNameInput = addInput('input', 'companyName', 'Company Name: ', undefined, useURLParams && urlParams.get('companyName'));
 		companyNameInput.style.visibility = 'hidden';
 		
 		selectorSubtypes.appendChild(companyNameInput);
 	}
 	else if(graphTypeSelector.value == "compRank")
 	{
-		selectorSubtypes.appendChild(addInput('select', 'month', 'Month: ', [monthsPretty, months], currentMonth));
+		selectorSubtypes.appendChild(addInput('select', 'month', 'Month: ', [monthsPretty, months], useURLParams && urlParams.has('month') ? urlParams.get('month') : currentMonth));
 		
 		// Username input and query button
-		const usernameInput = addInput('input', 'username', 'Username: ');
+		const usernameInput = addInput('input', 'username', 'Username: ', undefined, useURLParams && urlParams.get('username'));
 		
 		const submitButton = document.createElement("button");
 		submitButton.textContent = "Query";
@@ -121,12 +181,12 @@ function updateSelectors(graphTypeSelector, selectorSubtypes)
 		selectorSubtypes.appendChild(usernameInput);
 		
 		// Hidden company ID input, autofilled by query
-		const companyIDInput = addInput('input', 'companyID', 'Company ID: ');
+		const companyIDInput = addInput('input', 'companyID', 'Company ID: ', undefined, useURLParams && urlParams.get('companyID'));
 		companyIDInput.style.visibility = 'hidden';
 		
 		selectorSubtypes.appendChild(companyIDInput);
 		
-		const companyNameInput = addInput('input', 'companyName', 'Company Name: ');
+		const companyNameInput = addInput('input', 'companyName', 'Company Name: ', undefined, useURLParams && urlParams.get('companyName'));
 		companyNameInput.style.visibility = 'hidden';
 		
 		selectorSubtypes.appendChild(companyNameInput);
@@ -167,7 +227,7 @@ function switchPlot()
 			break;
 		case "matHistory":
 			metricElem = document.getElementById("metric");
-			matElem = document.getElementById("mat");
+			matElem = document.getElementById("ticker");
 			promiseGenerateMatGraph("mainPlot", matElem.value, metricElem.value, months);
 			break;
 		case "compTotals":
@@ -190,6 +250,44 @@ function switchPlot()
 			idElem = document.getElementById("companyID");
 			promiseGenerateRankChart("mainPlot", nameElem.value, idElem.value, monthElem.value, months);
 	}
+
+	updatePermalink(typeElem);
+}
+
+function updatePermalink(typeElem)
+{
+	const permalinkInput = document.getElementById("permalink")
+	const hideOptionsButton = document.getElementById("hideOptions");
+	const latestMonthButton = document.getElementById("latestMonth");
+	
+	var permalink = "https://pmmg-products.github.io/reports/?type=" + typeElem.value
+	
+	const relevantSubtypes = {
+		"topProduction": ["metric", "month"],
+		"topCompanies": ["metric", "month"],
+		"matHistory": ["metric", "ticker"],
+		"compTotals": ["chartType", "metric", "month", "username", "companyName", "companyID"],
+		"compHistory": ["metric", "username", "companyName", "companyID"],
+		"compRank": ["month", "username", "companyName", "companyID"]
+	}
+	
+	relevantSubtypes[typeElem.value].forEach(subtype => {
+		if(subtype == "month" && latestMonthButton.checked){return;}
+		
+		const inputElem = document.getElementById(subtype);
+		if(inputElem.value && inputElem.value != "")
+		{
+			permalink += "&" + subtype + "=" + inputElem.value
+		}
+	});
+
+	if(hideOptionsButton.checked)
+	{
+		permalink += "&hideOptions"
+	}
+
+	permalinkInput.value = permalink;
+	return;
 }
 
 function promiseGenerateRankChart(container, companyName, companyID, currentMonth, months)
@@ -197,75 +295,89 @@ function promiseGenerateRankChart(container, companyName, companyID, currentMont
 	const monthIndex = months.indexOf(currentMonth);
 	const prevMonth = months[monthIndex == 0 ? 0 : monthIndex - 1];	// Get previous month to determine change
 	
-	fetch('data/company-data-' + currentMonth + '.json?cb=' + Date.now())
-    .then(response => response.json())  // Parse JSON data
-    .then(currentData => {
-		fetch('data/company-data-' + prevMonth + '.json?cb=' + Date.now())
-		.then(response => response.json())
-		.then(prevData => {
-			generateRankChart(container, currentData.individual[companyID], (monthIndex == 0 ? undefined : prevData.individual[companyID]), companyName, currentMonth);  // Use the JSON data
-		});
-    });
+	(async () => {
+		if(!loadedData['company-data-' + currentMonth])
+		{
+			loadedData['company-data-' + currentMonth] = await fetch('data/company-data-' + currentMonth + '.json?cb=' + Date.now()).then(response => response.json())
+		}
+		
+		if(!loadedData['company-data-' + prevMonth])
+		{
+			loadedData['company-data-' + prevMonth] = await fetch('data/company-data-' + prevMonth + '.json?cb=' + Date.now()).then(response => response.json())
+		}
+		
+		generateRankChart(container, loadedData['company-data-' + currentMonth].individual[companyID], (monthIndex == 0 ? undefined : loadedData['company-data-' + prevMonth].individual[companyID]), companyName, currentMonth);  // Use the JSON data
+	})();
 }
 
 function promiseGenerateCompanyHistoryGraph(container, companyName, companyID, metric, months)	// Metric is either 'profit' or 'volume'
 {
 	if(!companyID){return;}
 	const validMonths = [];
+	const data = []
+	var hasData = false;
 	
-	// Get data
-	const fetches = months.map(month => 
-		fetch('data/company-data-' + month + '.json?cb=' + Date.now()).then(res => res.json()).then(json => ({ "month": month, "monthData": json }))
-	);
-	
-	Promise.all(fetches).then(rawData => {
-		const data = []
-		var hasData = false;
-		
-		rawData.forEach(({month, monthData}) => {
-			const dataPoint = monthData.totals[companyID]
+	(async () => {
+		for(const month of months) {
+			if(!loadedData['company-data-' + month])
+			{
+				loadedData['company-data-' + month] = await fetch('data/company-data-' + month + '.json?cb=' + Date.now()).then(response => response.json())
+			}
+			
+			const dataPoint = loadedData['company-data-' + month].totals[companyID]
 			if(dataPoint)
 			{
 				data.push(dataPoint[metric])
 				validMonths.push(month);
 				hasData = true;
 			}
-		});
+		};
 		
 		if(hasData)
 		{
 			generateCompanyHistoryGraph(container, months.map(month => prettyMonthName(month)), data, companyName, metric)
 		}
-	});
+	})();
 }
 
 function promiseGenerateCompanyGraph(container, chartType, companyName, companyID, month, metric)	// Metric is either 'profit' or 'volume'. chartType is either 'bar' or 'pie'
 {
-	fetch('data/company-data-' + month + '.json?cb=' + Date.now())
-    .then(response => response.json())  // Parse JSON data
-    .then(data => {
-		generateCompanyGraph(container, chartType, data.individual[companyID], companyName, month, metric);  // Use the JSON data
-    });
+	(async () => {
+		if(!loadedData['company-data-' + month])
+		{
+			loadedData['company-data-' + month] = await fetch('data/company-data-' + month + '.json?cb=' + Date.now()).then(response => response.json())
+		}
+		
+		generateCompanyGraph(container, chartType, loadedData['company-data-' + month].individual[companyID], companyName, month, metric);  // Use the JSON data
+	})();
 }
 
 function promiseGenerateTopCompanyGraph(container, month, metric)	// Metric is either 'profit' or 'volume'
 {
-	fetch('data/company-data-' + month + '.json?cb=' + Date.now())
-    .then(response => response.json())  // Parse JSON data
-    .then(data => {
-		fetch('data/knownCompanies2.json?cb=' + Date.now())
-		.then(response => response.json())
-		.then(knownCompanies => {
-			generateTopCompanyGraph(container, data, knownCompanies, month, metric);  // Use the JSON data
-		});
-    });
+	(async () => {
+		if(!loadedData['company-data-' + month])
+		{
+			loadedData['company-data-' + month] = await fetch('data/company-data-' + month + '.json?cb=' + Date.now()).then(response => response.json())
+		}
+		
+		if(!loadedData['known-companies'])
+		{
+			loadedData['known-companies'] = await fetch('data/knownCompanies2.json?cb=' + Date.now()).then(response => response.json())
+		}
+		
+		generateTopCompanyGraph(container, loadedData['company-data-' + month], loadedData['known-companies'], month, metric);  // Use the JSON data
+	})();
 }
 
 function promiseGenerateTopProdGraph(container, month, metric)	// Metric is either 'profit' or 'volume'
 {
-	fetch('data/prod-data-' + month + '.json?cb=' + Date.now())
-    .then(response => response.json())  // Parse JSON data
-    .then(data => {
+	(async () => {
+		if(!loadedData['prod-data-' + month])
+		{
+			loadedData['prod-data-' + month] = await fetch('data/prod-data-' + month + '.json?cb=' + Date.now()).then(response => response.json())
+		}
+		
+		const data = loadedData['prod-data-' + month];
 		if(metric == 'deficit')	// Populate deficit into data
 		{
 			Object.keys(data).forEach(ticker => {
@@ -274,7 +386,7 @@ function promiseGenerateTopProdGraph(container, month, metric)	// Metric is eith
 			});
 		}
 		generateTopProdGraph(container, data, month, metric);  // Use the JSON data
-    });
+	})();
 }
 
 function promiseGenerateMatGraph(container, ticker, metric, months)	// Metric is either 'profit', 'volume', or 'amount'
@@ -284,17 +396,16 @@ function promiseGenerateMatGraph(container, ticker, metric, months)	// Metric is
 	ticker = ticker.toUpperCase();
 	
 	const validMonths = [];
+	const data = [];
 	
-	// Get data
-	const fetches = months.map(month => 
-		fetch('data/prod-data-' + month + '.json?cb=' + Date.now()).then(res => res.json()).then(json => ({ "month": month, "monthData": json }))
-	);
-	
-	Promise.all(fetches).then(rawData => {
-		const data = []
-		var hasData = false;
-		rawData.forEach(({month, monthData}) => {
-			const dataPoint = monthData[ticker]
+	(async () => {
+		for(const month of months) {
+			if(!loadedData['prod-data-' + month])
+			{
+				loadedData['prod-data-' + month] = await fetch('data/prod-data-' + month + '.json?cb=' + Date.now()).then(response => response.json())
+			}
+			
+			const dataPoint = loadedData['prod-data-' + month][ticker]
 			if(dataPoint)
 			{
 				if(metric == 'price')
@@ -312,13 +423,13 @@ function promiseGenerateMatGraph(container, ticker, metric, months)	// Metric is
 				validMonths.push(month)
 				hasData = true;
 			}
-		});
+		};
 		
 		if(hasData)
 		{
 			generateMatGraph(container, validMonths.map(month => prettyMonthName(month)), data, ticker, metric)
 		}
-	});
+	})();
 }
 
 function generateTopProdGraph(container, prodData, month, metric)
@@ -848,10 +959,11 @@ function addInput(inputType, id, label, values, defaultValue)
 	if(inputType == 'select')
 	{
 		addOptions(inputElem, values[0], values[1]);
-		if(defaultValue)
-		{
-			inputElem.value = defaultValue;
-		}
+	}
+	
+	if(defaultValue)
+	{
+		inputElem.value = defaultValue;
 	}
 	
 	inputElem.addEventListener("change", function() {
@@ -866,25 +978,40 @@ function addInput(inputType, id, label, values, defaultValue)
 async function getCompanyInfo()
 {
 	const usernameInput = document.getElementById('username');
+	var companyID;
+	var companyName;
 	
 	if(!usernameInput.value){return;}
 	
-	fetch('https://rest.fnar.net/user/' + usernameInput.value)
-	  .then(response => response.json())
-	  .then(data => {
-			const companyID = data.CompanyId;
-			const companyName = data.UserName;
-			if(!companyID || !companyName){return;}
-			
-			const companyIDInput = document.getElementById('companyID');
-			companyIDInput.value = companyID;
-			
-			const companyNameInput = document.getElementById('companyName');
-			companyNameInput.value = companyName;
-			
-			switchPlot();
-	  })
-	  .catch(error => {alert('Bad Response: Check Username'); console.error(error)});
+	(async () => {
+		if(!loadedData['known-companies'])
+		{
+			loadedData['known-companies'] = await fetch('data/knownCompanies2.json?cb=' + Date.now()).then(response => response.json())
+		}
+		
+		const match = Object.entries(loadedData['known-companies']).find(([id, username]) => username && (username.toLowerCase() === usernameInput.value.toLowerCase()));
+		
+		if(match)
+		{
+			[companyID, companyName] = match;
+		}
+		else
+		{
+			const fioResult = fetch('https://rest.fnar.net/user/' + usernameInput.value).then(response => response.json()).catch(error => {alert('Bad Response: Check Username'); console.error(error)});
+			companyID = fioResult.CompanyId;
+			companyName = fioResult.UserName;
+		}
+		
+		if(!companyID || !companyName){return;}
+		
+		const companyIDInput = document.getElementById('companyID');
+		companyIDInput.value = companyID;
+		
+		const companyNameInput = document.getElementById('companyName');
+		companyNameInput.value = companyName;
+		
+		switchPlot();
+	})();
 }
 
 const fullMonthNames = {

+ 0 - 0
material-info.js → reports/material-info.js


+ 65 - 7
styles.css → reports/styles.css

@@ -9,17 +9,23 @@ body {
 	display: flex;
 	margin: 1px;
 }
+.topTabContainer div {
+	position: relative;
+}
+
 .topTab {
 	background-color: #252525;
 	margin: 1px;
 	cursor: pointer;
+	display: inline-flex;
+	flex-direction: column;
 }
 .topTabLink {
 	color: rgb(153, 153, 153);
 	padding: 4px 4px 2px 4px;
     text-decoration: none;
 }
-.topTabLink:hover {
+.topTab:hover a.topTabLink {
 	color: #eee;
 	transition: color 0.2s ease;
 }
@@ -29,12 +35,61 @@ body {
 	width: 100%;
 	height: 2px;
 }
-.toggleIndicatorActive {
+.topTab:hover div.toggleIndicator {
 	background-color: rgb(247, 166, 0);
 	-webkit-box-shadow: 0 0 6px 0 #f7a600;
     -moz-box-shadow: 0 0 6px 0 #f7a600;
     box-shadow: 0 0 6px 0 #f7a600;
+	transition: color 0.2s ease;
+}
+.permalinkContainer {
+	position: absolute !important;
+	z-index: 1000;
+	background: rgb(38, 38, 38);
+	width: 300px;
+	height: 62px;
+	top: 33px;
+	border-color: rgb(196, 132, 0);
+	border-width: 3px;
+	border-style: solid;
+	border-radius: 4px;
+	
+}
+.permalinkCaret {
+  position: relative;
+  top: -10px;
+  left: 13%; /* position relative to popup */
+  width: 0;
+  height: 0;
+  border-left: 10px solid transparent;
+  border-right: 10px solid transparent;
+  border-bottom: 10px solid rgb(196, 132, 0); /* match popup background */
+}
+
+.permalinkInner {
+	margin-right: 7px;
+}
+
+.permalinkInner div {
+	width: 100%;
+	height: 100%;
+	margin-top: -5px;
+	margin-left: 3px;
+	margin-right: 3px;
+	display: flex;
+	margin-bottom: 3px;
+}
+
+.permalinkInner div div {
+	margin-top: 1px;
+	color: #ccc;
 }
+
+.permalinkInner input {
+	flex: 1;
+	margin-right: 3px;
+}
+
 .plotly-notifier {
     display: none;
 }
@@ -57,13 +112,13 @@ body {
 	margin-top: 10px;
 }
 
-.plotSelector {
+input, select {
 	color: #bbb;
 	background-color: #42361d;
 	border: none;
 	border-bottom: 1px solid #8d6411;
 }
-.plotSelector:focus {
+input:focus, select:focus {
 	outline: none;
 }
 
@@ -79,6 +134,10 @@ body {
 }
 
 .queryButton {
+	margin-left: 5px;
+}
+
+button {
 	background-color: #f7a600;
 	display: inline-block;
 	border: none;
@@ -90,10 +149,9 @@ body {
 	text-transform: uppercase;
 	cursor: pointer;
 	outline: none;
-	margin-left: 5px;
 }
 
-.queryButton:hover {
+button:hover {
 	color: rgb(39, 39, 39);
 	transition: color 0.2s ease;
 }
@@ -138,7 +196,7 @@ select {
 	cursor: pointer;
 }
 
-input {
+input.plotSelector {
 	width: 134px;
 	cursor: text;
 }

部分文件因文件數量過多而無法顯示