|
@@ -0,0 +1,1825 @@
|
|
|
|
|
+// src/permalink.ts
|
|
|
|
|
+function addPermalink() {
|
|
|
|
|
+ const permalinkContainer = document.getElementById("permalinkContainer");
|
|
|
|
|
+ const permalinkButton = document.getElementById("permalinkButton");
|
|
|
|
|
+ const permalinkCopyButton = document.getElementById("permalinkCopyButton");
|
|
|
|
|
+ const rprunCopyButton = document.getElementById("permalinkCopyButton-rprun");
|
|
|
|
|
+ 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);
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+ rprunCopyButton.addEventListener("click", function() {
|
|
|
|
|
+ const permalinkElem = document.getElementById("permalink-rprun");
|
|
|
|
|
+ if (permalinkElem.value && permalinkElem.value != "") {
|
|
|
|
|
+ navigator.clipboard.writeText(permalinkElem.value);
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+ permalinkOptionsButton.addEventListener("change", function() {
|
|
|
|
|
+ updatePermalink();
|
|
|
|
|
+ });
|
|
|
|
|
+ permalinkLatestMonth.addEventListener("change", function() {
|
|
|
|
|
+ updatePermalink();
|
|
|
|
|
+ });
|
|
|
|
|
+}
|
|
|
|
|
+function updatePermalink() {
|
|
|
|
|
+ const graphSelect = document.getElementById("graphType");
|
|
|
|
|
+ const permalinkInput = document.getElementById("permalink");
|
|
|
|
|
+ const rprunInput = document.getElementById("permalink-rprun");
|
|
|
|
|
+ const hideOptionsButton = document.getElementById("hideOptions");
|
|
|
|
|
+ const latestMonthButton = document.getElementById("latestMonth");
|
|
|
|
|
+ var permalink = "https://prun.raylu.net/stats/?type=" + graphSelect.value;
|
|
|
|
|
+ var rprunLink = "XIT PRUNSTATS type-" + graphSelect.value;
|
|
|
|
|
+ const graph = graphs.find((obj) => obj.id == graphSelect.value);
|
|
|
|
|
+ graph?.configFieldIDs.forEach((subtype) => {
|
|
|
|
|
+ if (subtype == "month" && latestMonthButton.checked) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ const inputElem = document.getElementById(subtype);
|
|
|
|
|
+ if (inputElem.value && inputElem.value != "") {
|
|
|
|
|
+ permalink += "&" + subtype + "=" + inputElem.value;
|
|
|
|
|
+ rprunLink += " " + subtype + "-" + inputElem.value;
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+ if (hideOptionsButton.checked) {
|
|
|
|
|
+ permalink += "&hideOptions";
|
|
|
|
|
+ rprunLink += " hideOptions";
|
|
|
|
|
+ }
|
|
|
|
|
+ permalinkInput.value = permalink;
|
|
|
|
|
+ rprunInput.value = rprunLink;
|
|
|
|
|
+ return;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// src/staticData/constants.ts
|
|
|
|
|
+var months = ["mar25", "apr25", "may25", "jun25", "jul25", "aug25", "sep25", "oct25", "nov25", "dec25", "jan26", "feb26", "mar26", "apr26", "may26", "jun26"];
|
|
|
|
|
+var monthsPretty = ["March 3025", "April 3025", "May 3025", "June 3025", "July 3025", "August 3025", "September 3025", "October 3025", "November 3025", "December 3025", "January 3026", "February 3026", "March 3026", "April 3026", "May 3026", "June 3026"];
|
|
|
|
|
+var fullMonthNames = {
|
|
|
|
|
+ jan: "January",
|
|
|
|
|
+ feb: "February",
|
|
|
|
|
+ mar: "March",
|
|
|
|
|
+ apr: "April",
|
|
|
|
|
+ may: "May",
|
|
|
|
|
+ jun: "June",
|
|
|
|
|
+ jul: "July",
|
|
|
|
|
+ aug: "August",
|
|
|
|
|
+ sep: "September",
|
|
|
|
|
+ oct: "October",
|
|
|
|
|
+ nov: "November",
|
|
|
|
|
+ dec: "December"
|
|
|
|
|
+};
|
|
|
|
|
+var prettyModeNames = {
|
|
|
|
|
+ amount: "Amount",
|
|
|
|
|
+ profit: "Profit",
|
|
|
|
|
+ volume: "Volume",
|
|
|
|
|
+ price: "Price",
|
|
|
|
|
+ deficit: "Deficit",
|
|
|
|
|
+ bases: "Bases",
|
|
|
|
|
+ ships: "Ships"
|
|
|
|
|
+};
|
|
|
|
|
+var materialCategories = {
|
|
|
|
|
+ "Consumables (Luxury)": [
|
|
|
|
|
+ "ALE",
|
|
|
|
|
+ "COF",
|
|
|
|
|
+ "GIN",
|
|
|
|
|
+ "KOM",
|
|
|
|
|
+ "NST",
|
|
|
|
|
+ "PWO",
|
|
|
|
|
+ "REP",
|
|
|
|
|
+ "SC",
|
|
|
|
|
+ "VG",
|
|
|
|
|
+ "WIN"
|
|
|
|
|
+ ],
|
|
|
|
|
+ "Ship Engines": [
|
|
|
|
|
+ "AEN",
|
|
|
|
|
+ "AFP",
|
|
|
|
|
+ "AFR",
|
|
|
|
|
+ "ANZ",
|
|
|
|
|
+ "BFP",
|
|
|
|
|
+ "BFR",
|
|
|
|
|
+ "CHA",
|
|
|
|
|
+ "ENG",
|
|
|
|
|
+ "FIR",
|
|
|
|
|
+ "FSE",
|
|
|
|
|
+ "GCH",
|
|
|
|
|
+ "GEN",
|
|
|
|
|
+ "GNZ",
|
|
|
|
|
+ "HNZ",
|
|
|
|
|
+ "HPR",
|
|
|
|
|
+ "HTE",
|
|
|
|
|
+ "HYR",
|
|
|
|
|
+ "LFE",
|
|
|
|
|
+ "LFP",
|
|
|
|
|
+ "MFE",
|
|
|
|
|
+ "NOZ",
|
|
|
|
|
+ "QCR",
|
|
|
|
|
+ "RAG",
|
|
|
|
|
+ "RCS",
|
|
|
|
|
+ "RCT",
|
|
|
|
|
+ "SFE",
|
|
|
|
|
+ "VOE",
|
|
|
|
|
+ "VOR"
|
|
|
|
|
+ ],
|
|
|
|
|
+ "Software Tools": [
|
|
|
|
|
+ "DA",
|
|
|
|
|
+ "DD",
|
|
|
|
|
+ "DV",
|
|
|
|
|
+ "EDC",
|
|
|
|
|
+ "NN",
|
|
|
|
|
+ "OS"
|
|
|
|
|
+ ],
|
|
|
|
|
+ "Construction Parts": [
|
|
|
|
|
+ "AEF",
|
|
|
|
|
+ "AIR",
|
|
|
|
|
+ "DEC",
|
|
|
|
|
+ "FC",
|
|
|
|
|
+ "FLO",
|
|
|
|
|
+ "FLP",
|
|
|
|
|
+ "GC",
|
|
|
|
|
+ "GV",
|
|
|
|
|
+ "LIT",
|
|
|
|
|
+ "MGC",
|
|
|
|
|
+ "MHL",
|
|
|
|
|
+ "PSH",
|
|
|
|
|
+ "RSH",
|
|
|
|
|
+ "TCS",
|
|
|
|
|
+ "TRU",
|
|
|
|
|
+ "TSH"
|
|
|
|
|
+ ],
|
|
|
|
|
+ Alloys: [
|
|
|
|
|
+ "ALR",
|
|
|
|
|
+ "AST",
|
|
|
|
|
+ "BGO",
|
|
|
|
|
+ "BOS",
|
|
|
|
|
+ "BRO",
|
|
|
|
|
+ "FAL",
|
|
|
|
|
+ "FET",
|
|
|
|
|
+ "RGO",
|
|
|
|
|
+ "WAL",
|
|
|
|
|
+ "WRH"
|
|
|
|
|
+ ],
|
|
|
|
|
+ "Consumable Bundles": [
|
|
|
|
|
+ "CBU",
|
|
|
|
|
+ "EBU",
|
|
|
|
|
+ "PBU",
|
|
|
|
|
+ "SBU",
|
|
|
|
|
+ "TBU"
|
|
|
|
|
+ ],
|
|
|
|
|
+ "Medical Equipment": [
|
|
|
|
|
+ "ADR",
|
|
|
|
|
+ "BND",
|
|
|
|
|
+ "PK",
|
|
|
|
|
+ "SEQ",
|
|
|
|
|
+ "STR",
|
|
|
|
|
+ "TUB"
|
|
|
|
|
+ ],
|
|
|
|
|
+ "Electronic Parts": [
|
|
|
|
|
+ "CD",
|
|
|
|
|
+ "DIS",
|
|
|
|
|
+ "FAN",
|
|
|
|
|
+ "MB",
|
|
|
|
|
+ "MPC",
|
|
|
|
|
+ "PCB",
|
|
|
|
|
+ "RAM",
|
|
|
|
|
+ "ROM",
|
|
|
|
|
+ "SEN",
|
|
|
|
|
+ "TPU",
|
|
|
|
|
+ "TRA"
|
|
|
|
|
+ ],
|
|
|
|
|
+ "Energy Systems": [
|
|
|
|
|
+ "CBL",
|
|
|
|
|
+ "CBM",
|
|
|
|
|
+ "CBS",
|
|
|
|
|
+ "POW",
|
|
|
|
|
+ "SOL",
|
|
|
|
|
+ "SP"
|
|
|
|
|
+ ],
|
|
|
|
|
+ Minerals: [
|
|
|
|
|
+ "BER",
|
|
|
|
|
+ "BOR",
|
|
|
|
|
+ "BRM",
|
|
|
|
|
+ "CLI",
|
|
|
|
|
+ "GAL",
|
|
|
|
|
+ "HAL",
|
|
|
|
|
+ "LST",
|
|
|
|
|
+ "MAG",
|
|
|
|
|
+ "MGS",
|
|
|
|
|
+ "SCR",
|
|
|
|
|
+ "TAI",
|
|
|
|
|
+ "TCO",
|
|
|
|
|
+ "TS",
|
|
|
|
|
+ "ZIR"
|
|
|
|
|
+ ],
|
|
|
|
|
+ "Construction Materials": [
|
|
|
|
|
+ "CMK",
|
|
|
|
|
+ "EPO",
|
|
|
|
|
+ "GL",
|
|
|
|
|
+ "INS",
|
|
|
|
|
+ "MCG",
|
|
|
|
|
+ "MTC",
|
|
|
|
|
+ "NCS",
|
|
|
|
|
+ "NFI",
|
|
|
|
|
+ "NG",
|
|
|
|
|
+ "RG",
|
|
|
|
|
+ "SEA"
|
|
|
|
|
+ ],
|
|
|
|
|
+ "Consumables (Basic)": [
|
|
|
|
|
+ "DW",
|
|
|
|
|
+ "EXO",
|
|
|
|
|
+ "FIM",
|
|
|
|
|
+ "HMS",
|
|
|
|
|
+ "HSS",
|
|
|
|
|
+ "LC",
|
|
|
|
|
+ "MEA",
|
|
|
|
|
+ "MED",
|
|
|
|
|
+ "OVE",
|
|
|
|
|
+ "PDA",
|
|
|
|
|
+ "PT",
|
|
|
|
|
+ "RAT",
|
|
|
|
|
+ "SCN",
|
|
|
|
|
+ "WS"
|
|
|
|
|
+ ],
|
|
|
|
|
+ "Software Systems": [
|
|
|
|
|
+ "IDC",
|
|
|
|
|
+ "IMM",
|
|
|
|
|
+ "SNM",
|
|
|
|
|
+ "WAI"
|
|
|
|
|
+ ],
|
|
|
|
|
+ "Electronic Pieces": [
|
|
|
|
|
+ "BCO",
|
|
|
|
|
+ "BGC",
|
|
|
|
|
+ "CAP",
|
|
|
|
|
+ "HCC",
|
|
|
|
|
+ "LDI",
|
|
|
|
|
+ "MFK",
|
|
|
|
|
+ "MWF",
|
|
|
|
|
+ "SFK",
|
|
|
|
|
+ "SWF",
|
|
|
|
|
+ "TRN"
|
|
|
|
|
+ ],
|
|
|
|
|
+ "Software Components": [
|
|
|
|
|
+ "BAI",
|
|
|
|
|
+ "LD",
|
|
|
|
|
+ "MLI",
|
|
|
|
|
+ "NF",
|
|
|
|
|
+ "SA",
|
|
|
|
|
+ "SAL",
|
|
|
|
|
+ "WM"
|
|
|
|
|
+ ],
|
|
|
|
|
+ Ores: [
|
|
|
|
|
+ "ALO",
|
|
|
|
|
+ "AUO",
|
|
|
|
|
+ "CUO",
|
|
|
|
|
+ "FEO",
|
|
|
|
|
+ "LIO",
|
|
|
|
|
+ "REO",
|
|
|
|
|
+ "SIO",
|
|
|
|
|
+ "TIO",
|
|
|
|
|
+ "REO"
|
|
|
|
|
+ ],
|
|
|
|
|
+ "Unit Prefabs": [
|
|
|
|
|
+ "BR1",
|
|
|
|
|
+ "BR2",
|
|
|
|
|
+ "BRS",
|
|
|
|
|
+ "BSU",
|
|
|
|
|
+ "CPU",
|
|
|
|
|
+ "CQL",
|
|
|
|
|
+ "CQM",
|
|
|
|
|
+ "CQS",
|
|
|
|
|
+ "CQT",
|
|
|
|
|
+ "DOU",
|
|
|
|
|
+ "FUN",
|
|
|
|
|
+ "HAB",
|
|
|
|
|
+ "LU",
|
|
|
|
|
+ "RDL",
|
|
|
|
|
+ "RDS",
|
|
|
|
|
+ "SU",
|
|
|
|
|
+ "TCU",
|
|
|
|
|
+ "WOR",
|
|
|
|
|
+ "BSU",
|
|
|
|
|
+ "CPU"
|
|
|
|
|
+ ],
|
|
|
|
|
+ "Ship Shields": [
|
|
|
|
|
+ "APT",
|
|
|
|
|
+ "ARP",
|
|
|
|
|
+ "AWH",
|
|
|
|
|
+ "BPT",
|
|
|
|
|
+ "BRP",
|
|
|
|
|
+ "BWH",
|
|
|
|
|
+ "SRP"
|
|
|
|
|
+ ],
|
|
|
|
|
+ "Electronic Devices": [
|
|
|
|
|
+ "AAR",
|
|
|
|
|
+ "AWF",
|
|
|
|
|
+ "BID",
|
|
|
|
|
+ "BMF",
|
|
|
|
|
+ "BSC",
|
|
|
|
|
+ "BWS",
|
|
|
|
|
+ "HD",
|
|
|
|
|
+ "HOG",
|
|
|
|
|
+ "HPC",
|
|
|
|
|
+ "MHP",
|
|
|
|
|
+ "RAD",
|
|
|
|
|
+ "SAR"
|
|
|
|
|
+ ],
|
|
|
|
|
+ Metals: [
|
|
|
|
|
+ "AL",
|
|
|
|
|
+ "AU",
|
|
|
|
|
+ "CU",
|
|
|
|
|
+ "FE",
|
|
|
|
|
+ "LI",
|
|
|
|
|
+ "RE",
|
|
|
|
|
+ "SI",
|
|
|
|
|
+ "STL",
|
|
|
|
|
+ "TI",
|
|
|
|
|
+ "W",
|
|
|
|
|
+ "RE"
|
|
|
|
|
+ ],
|
|
|
|
|
+ "Electronic Systems": [
|
|
|
|
|
+ "ACS",
|
|
|
|
|
+ "ADS",
|
|
|
|
|
+ "CC",
|
|
|
|
|
+ "COM",
|
|
|
|
|
+ "CRU",
|
|
|
|
|
+ "FFC",
|
|
|
|
|
+ "LIS",
|
|
|
|
|
+ "LOG",
|
|
|
|
|
+ "STS",
|
|
|
|
|
+ "TAC",
|
|
|
|
|
+ "WR"
|
|
|
|
|
+ ],
|
|
|
|
|
+ Textiles: [
|
|
|
|
|
+ "CF",
|
|
|
|
|
+ "COT",
|
|
|
|
|
+ "CTF",
|
|
|
|
|
+ "KV",
|
|
|
|
|
+ "NL",
|
|
|
|
|
+ "SIL",
|
|
|
|
|
+ "TK"
|
|
|
|
|
+ ],
|
|
|
|
|
+ Plastics: [
|
|
|
|
|
+ "DCL",
|
|
|
|
|
+ "DCM",
|
|
|
|
|
+ "DCS",
|
|
|
|
|
+ "PE",
|
|
|
|
|
+ "PG",
|
|
|
|
|
+ "PSL",
|
|
|
|
|
+ "PSM",
|
|
|
|
|
+ "PSS"
|
|
|
|
|
+ ],
|
|
|
|
|
+ Chemicals: [
|
|
|
|
|
+ "BAC",
|
|
|
|
|
+ "BL",
|
|
|
|
|
+ "BLE",
|
|
|
|
|
+ "CST",
|
|
|
|
|
+ "DDT",
|
|
|
|
|
+ "EES",
|
|
|
|
|
+ "ETC",
|
|
|
|
|
+ "FLX",
|
|
|
|
|
+ "IND",
|
|
|
|
|
+ "JUI",
|
|
|
|
|
+ "KRE",
|
|
|
|
|
+ "LCR",
|
|
|
|
|
+ "NAB",
|
|
|
|
|
+ "NR",
|
|
|
|
|
+ "NS",
|
|
|
|
|
+ "OLF",
|
|
|
|
|
+ "PFE",
|
|
|
|
|
+ "REA",
|
|
|
|
|
+ "SOI",
|
|
|
|
|
+ "TCL",
|
|
|
|
|
+ "THF",
|
|
|
|
|
+ "KRE"
|
|
|
|
|
+ ],
|
|
|
|
|
+ Elements: [
|
|
|
|
|
+ "BE",
|
|
|
|
|
+ "C",
|
|
|
|
|
+ "CA",
|
|
|
|
|
+ "CL",
|
|
|
|
|
+ "ES",
|
|
|
|
|
+ "I",
|
|
|
|
|
+ "MG",
|
|
|
|
|
+ "NA",
|
|
|
|
|
+ "S",
|
|
|
|
|
+ "TA",
|
|
|
|
|
+ "TC",
|
|
|
|
|
+ "ZR"
|
|
|
|
|
+ ],
|
|
|
|
|
+ Gases: [
|
|
|
|
|
+ "AMM",
|
|
|
|
|
+ "AR",
|
|
|
|
|
+ "F",
|
|
|
|
|
+ "H",
|
|
|
|
|
+ "HE",
|
|
|
|
|
+ "HE3",
|
|
|
|
|
+ "KR",
|
|
|
|
|
+ "N",
|
|
|
|
|
+ "NE",
|
|
|
|
|
+ "O",
|
|
|
|
|
+ "KR"
|
|
|
|
|
+ ],
|
|
|
|
|
+ "Ship Parts": [
|
|
|
|
|
+ "AGS",
|
|
|
|
|
+ "AHP",
|
|
|
|
|
+ "ATP",
|
|
|
|
|
+ "BGS",
|
|
|
|
|
+ "BHP",
|
|
|
|
|
+ "HAM",
|
|
|
|
|
+ "HHP",
|
|
|
|
|
+ "LHP",
|
|
|
|
|
+ "NV1",
|
|
|
|
|
+ "NV2",
|
|
|
|
|
+ "RHP",
|
|
|
|
|
+ "SSC",
|
|
|
|
|
+ "THP",
|
|
|
|
|
+ "HAM"
|
|
|
|
|
+ ],
|
|
|
|
|
+ Drones: [
|
|
|
|
|
+ "CCD",
|
|
|
|
|
+ "DCH",
|
|
|
|
|
+ "DRF",
|
|
|
|
|
+ "RED",
|
|
|
|
|
+ "SDR",
|
|
|
|
|
+ "SRD",
|
|
|
|
|
+ "SUD"
|
|
|
|
|
+ ],
|
|
|
|
|
+ "Agricultural Products": [
|
|
|
|
|
+ "ALG",
|
|
|
|
|
+ "BEA",
|
|
|
|
|
+ "CAF",
|
|
|
|
|
+ "FOD",
|
|
|
|
|
+ "GRA",
|
|
|
|
|
+ "GRN",
|
|
|
|
|
+ "HCP",
|
|
|
|
|
+ "HER",
|
|
|
|
|
+ "HOP",
|
|
|
|
|
+ "MAI",
|
|
|
|
|
+ "MTP",
|
|
|
|
|
+ "MUS",
|
|
|
|
|
+ "NUT",
|
|
|
|
|
+ "PIB",
|
|
|
|
|
+ "PPA",
|
|
|
|
|
+ "RCO",
|
|
|
|
|
+ "RSI",
|
|
|
|
|
+ "VEG",
|
|
|
|
|
+ "VIT"
|
|
|
|
|
+ ],
|
|
|
|
|
+ "Construction Prefabs": [
|
|
|
|
|
+ "ABH",
|
|
|
|
|
+ "ADE",
|
|
|
|
|
+ "ASE",
|
|
|
|
|
+ "ATA",
|
|
|
|
|
+ "BBH",
|
|
|
|
|
+ "BDE",
|
|
|
|
|
+ "BSE",
|
|
|
|
|
+ "BTA",
|
|
|
|
|
+ "HSE",
|
|
|
|
|
+ "LBH",
|
|
|
|
|
+ "LDE",
|
|
|
|
|
+ "LSE",
|
|
|
|
|
+ "LTA",
|
|
|
|
|
+ "RBH",
|
|
|
|
|
+ "RDE",
|
|
|
|
|
+ "RSE",
|
|
|
|
|
+ "RTA"
|
|
|
|
|
+ ],
|
|
|
|
|
+ Fuels: [
|
|
|
|
|
+ "FF",
|
|
|
|
|
+ "SF",
|
|
|
|
|
+ "VF"
|
|
|
|
|
+ ],
|
|
|
|
|
+ "Ship Kits": [
|
|
|
|
|
+ "HCB",
|
|
|
|
|
+ "LCB",
|
|
|
|
|
+ "LFL",
|
|
|
|
|
+ "LSL",
|
|
|
|
|
+ "MCB",
|
|
|
|
|
+ "MFL",
|
|
|
|
|
+ "MSL",
|
|
|
|
|
+ "SCB",
|
|
|
|
|
+ "SFL",
|
|
|
|
|
+ "SSL",
|
|
|
|
|
+ "TCB",
|
|
|
|
|
+ "VCB",
|
|
|
|
|
+ "VFT",
|
|
|
|
|
+ "VSC",
|
|
|
|
|
+ "WCB",
|
|
|
|
|
+ "VFT"
|
|
|
|
|
+ ],
|
|
|
|
|
+ Liquids: [
|
|
|
|
|
+ "BTS",
|
|
|
|
|
+ "H2O",
|
|
|
|
|
+ "HEX",
|
|
|
|
|
+ "LES"
|
|
|
|
|
+ ],
|
|
|
|
|
+ Utility: [
|
|
|
|
|
+ "OFF",
|
|
|
|
|
+ "SUN",
|
|
|
|
|
+ "UTS"
|
|
|
|
|
+ ],
|
|
|
|
|
+ Infrastructure: [
|
|
|
|
|
+ "GWS",
|
|
|
|
|
+ "PFG",
|
|
|
|
|
+ "SDM",
|
|
|
|
|
+ "SST",
|
|
|
|
|
+ "SPT",
|
|
|
|
|
+ "TRS",
|
|
|
|
|
+ "TOR"
|
|
|
|
|
+ ]
|
|
|
|
|
+};
|
|
|
|
|
+var materialCategoryColors = {
|
|
|
|
|
+ "Agricultural Products": "#0a4708",
|
|
|
|
|
+ Alloys: "#946537",
|
|
|
|
|
+ Chemicals: "#d04774",
|
|
|
|
|
+ "Construction Materials": "#3174ec",
|
|
|
|
|
+ "Construction Parts": "#426684",
|
|
|
|
|
+ "Construction Prefabs": "#28377b",
|
|
|
|
|
+ "Consumable Bundles": "#57232a",
|
|
|
|
|
+ "Consumables (Basic)": "#ba363c",
|
|
|
|
|
+ "Consumables (Luxury)": "#680000",
|
|
|
|
|
+ Drones: "#a54d2b",
|
|
|
|
|
+ "Electronic Devices": "#6f2dac",
|
|
|
|
|
+ "Electronic Parts": "#7447d0",
|
|
|
|
|
+ "Electronic Pieces": "#906bd6",
|
|
|
|
|
+ "Electronic Systems": "#4c3365",
|
|
|
|
|
+ Elements: "#564739",
|
|
|
|
|
+ "Energy Systems": "#2e5740",
|
|
|
|
|
+ Fuels: "#6ba23c",
|
|
|
|
|
+ Gases: "#198284",
|
|
|
|
|
+ Liquids: "#6098c3",
|
|
|
|
|
+ "Medical Equipment": "#6ec36e",
|
|
|
|
|
+ Metals: "#4f4f4f",
|
|
|
|
|
+ Minerals: "#b28a62",
|
|
|
|
|
+ Ores: "#6b707a",
|
|
|
|
|
+ Plastics: "#791f62",
|
|
|
|
|
+ "Ship Engines": "#b24219",
|
|
|
|
|
+ "Ship Kits": "#b26d19",
|
|
|
|
|
+ "Ship Parts": "#b27c19",
|
|
|
|
|
+ "Ship Shields": "#d98c21",
|
|
|
|
|
+ "Software Components": "#a19248",
|
|
|
|
|
+ "Software Systems": "#554e1e",
|
|
|
|
|
+ "Software Tools": "#9a7b2c",
|
|
|
|
|
+ Textiles: "#6b733a",
|
|
|
|
|
+ "Unit Prefabs": "#363435",
|
|
|
|
|
+ Utility: "#baada1",
|
|
|
|
|
+ Infrastructure: "#252551"
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+// src/utils.ts
|
|
|
|
|
+function addOption(selector, displayName, id) {
|
|
|
|
|
+ const optionElem = document.createElement("option");
|
|
|
|
|
+ optionElem.textContent = displayName;
|
|
|
|
|
+ optionElem.value = id ?? displayName;
|
|
|
|
|
+ selector.appendChild(optionElem);
|
|
|
|
|
+}
|
|
|
|
|
+function clearChildren(elem) {
|
|
|
|
|
+ elem.textContent = "";
|
|
|
|
|
+ while (elem.children[0]) {
|
|
|
|
|
+ elem.removeChild(elem.children[0]);
|
|
|
|
|
+ }
|
|
|
|
|
+ return;
|
|
|
|
|
+}
|
|
|
|
|
+function addConfigField(inputType, id, label, value, defaultValue, updateFunc, marginShift) {
|
|
|
|
|
+ const labelElem = document.createElement("label");
|
|
|
|
|
+ labelElem.textContent = label;
|
|
|
|
|
+ labelElem.id = id + "-label";
|
|
|
|
|
+ const inputElem = document.createElement(inputType);
|
|
|
|
|
+ inputElem.id = id;
|
|
|
|
|
+ inputElem.classList.add("plotSelector");
|
|
|
|
|
+ if (inputType == "select") {
|
|
|
|
|
+ addOptions(inputElem, value.prettyValues, value.values);
|
|
|
|
|
+ }
|
|
|
|
|
+ if (defaultValue) {
|
|
|
|
|
+ inputElem.value = defaultValue;
|
|
|
|
|
+ }
|
|
|
|
|
+ inputElem.addEventListener("change", updateFunc);
|
|
|
|
|
+ labelElem.appendChild(inputElem);
|
|
|
|
|
+ const output = wrapInDiv(labelElem);
|
|
|
|
|
+ if (marginShift) {
|
|
|
|
|
+ output.style.marginLeft = marginShift;
|
|
|
|
|
+ }
|
|
|
|
|
+ return output;
|
|
|
|
|
+}
|
|
|
|
|
+function wrapInDiv(elem) {
|
|
|
|
|
+ const div = document.createElement("div");
|
|
|
|
|
+ div.appendChild(elem);
|
|
|
|
|
+ return div;
|
|
|
|
|
+}
|
|
|
|
|
+function addOptions(selector, prettyValues, values) {
|
|
|
|
|
+ for (var i = 0;i < prettyValues.length; i++) {
|
|
|
|
|
+ const optionElem = document.createElement("option");
|
|
|
|
|
+ optionElem.textContent = prettyValues[i];
|
|
|
|
|
+ optionElem.value = values ? values[i] : prettyValues[i];
|
|
|
|
|
+ selector.appendChild(optionElem);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+function deepMerge(target, source) {
|
|
|
|
|
+ for (const key in source) {
|
|
|
|
|
+ if (source[key] && typeof source[key] === "object" && !Array.isArray(source[key])) {
|
|
|
|
|
+ target[key] = deepMerge(target[key] || {}, source[key]);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ target[key] = source[key];
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return target;
|
|
|
|
|
+}
|
|
|
|
|
+function prettyMonthName(monthStr) {
|
|
|
|
|
+ const monthAbv = monthStr.substring(0, 3);
|
|
|
|
|
+ const monthNum = monthStr.substring(3);
|
|
|
|
|
+ return fullMonthNames[monthAbv] + " 30" + monthNum;
|
|
|
|
|
+}
|
|
|
|
|
+async function getData(loadedData, dataType, month) {
|
|
|
|
|
+ switch (dataType) {
|
|
|
|
|
+ case "prod":
|
|
|
|
|
+ case "company":
|
|
|
|
|
+ case "base":
|
|
|
|
|
+ case "ship":
|
|
|
|
|
+ if (!loadedData[dataType + "-data-" + month]) {
|
|
|
|
|
+ const response = await fetch(`data/${dataType}-data-${month}.json`);
|
|
|
|
|
+ if (response.status == 200) {
|
|
|
|
|
+ loadedData[dataType + "-data-" + month] = response.json();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return loadedData[dataType + "-data-" + month];
|
|
|
|
|
+ case "knownCompanies":
|
|
|
|
|
+ if (!loadedData["known-companies"]) {
|
|
|
|
|
+ loadedData["known-companies"] = await fetch("data/knownCompanies.json").then((response) => response.json());
|
|
|
|
|
+ }
|
|
|
|
|
+ return loadedData["known-companies"];
|
|
|
|
|
+ case "universe":
|
|
|
|
|
+ if (!loadedData["universe-data"]) {
|
|
|
|
|
+ loadedData["universe-data"] = await fetch("data/universe-data.json").then((response) => response.json());
|
|
|
|
|
+ }
|
|
|
|
|
+ return loadedData["universe-data"];
|
|
|
|
|
+ case "parentCorps":
|
|
|
|
|
+ if (!loadedData["parent-corps"]) {
|
|
|
|
|
+ loadedData["parent-corps"] = await fetch("data/parentCorps.json").then((response) => response.json());
|
|
|
|
|
+ }
|
|
|
|
|
+ return loadedData["parent-corps"];
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+function getMatCategory(ticker) {
|
|
|
|
|
+ return Object.entries(materialCategories).find(([_, tickers]) => tickers.includes(ticker))?.[0];
|
|
|
|
|
+}
|
|
|
|
|
+function getMatColor(ticker) {
|
|
|
|
|
+ return materialCategoryColors[getMatCategory(ticker) ?? ""] ?? "#000000";
|
|
|
|
|
+}
|
|
|
|
|
+async function getCompanyId(companyName, loadedData) {
|
|
|
|
|
+ const knownCompanies = await getData(loadedData, "knownCompanies");
|
|
|
|
|
+ var companyID = Object.entries(knownCompanies).find(([, v]) => v?.Username?.toLowerCase() === companyName.toLowerCase())?.[0];
|
|
|
|
|
+ if (companyID) {
|
|
|
|
|
+ return companyID;
|
|
|
|
|
+ }
|
|
|
|
|
+ console.log("Unknown username, querying FIO.");
|
|
|
|
|
+ const fioResult = await fetch("https://rest.fnar.net/user/" + companyName).then((response) => response.json()).catch((error) => {
|
|
|
|
|
+ alert("Bad Response: Check Username");
|
|
|
|
|
+ console.error(error);
|
|
|
|
|
+ });
|
|
|
|
|
+ companyID = fioResult?.CompanyId;
|
|
|
|
|
+ companyName = fioResult?.UserName;
|
|
|
|
|
+ knownCompanies[companyID] = companyName;
|
|
|
|
|
+ return companyID;
|
|
|
|
|
+}
|
|
|
|
|
+function updateUsernameLabel() {
|
|
|
|
|
+ const usernameLabel = document.getElementById("companyName-label");
|
|
|
|
|
+ const groupElem = document.getElementById("group");
|
|
|
|
|
+ if (usernameLabel && usernameLabel.firstChild) {
|
|
|
|
|
+ if (groupElem && groupElem.value && groupElem.value != "company") {
|
|
|
|
|
+ usernameLabel.firstChild.nodeValue = "Corp Code: ";
|
|
|
|
|
+ } else {
|
|
|
|
|
+ usernameLabel.firstChild.nodeValue = "Username: ";
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// src/core.ts
|
|
|
|
|
+function switchPlot() {
|
|
|
|
|
+ const graphSelect = document.getElementById("graphType");
|
|
|
|
|
+ const graph = graphs.find((obj) => obj.id == graphSelect.value);
|
|
|
|
|
+ const oldGraph = document.getElementById("mainPlot");
|
|
|
|
|
+ oldGraph?.remove();
|
|
|
|
|
+ const newGraph = document.createElement("div");
|
|
|
|
|
+ newGraph.id = "mainPlot";
|
|
|
|
|
+ const graphContainer = document.getElementById("mainPlotContainer");
|
|
|
|
|
+ graphContainer?.appendChild(newGraph);
|
|
|
|
|
+ const configValues = {};
|
|
|
|
|
+ graph?.configFieldIDs.forEach((id) => {
|
|
|
|
|
+ const inputElem = document.getElementById(id);
|
|
|
|
|
+ configValues[id] = inputElem?.value;
|
|
|
|
|
+ });
|
|
|
|
|
+ updatePermalink();
|
|
|
|
|
+ (async () => {
|
|
|
|
|
+ graph?.generatePlot(configValues, "mainPlot");
|
|
|
|
|
+ })();
|
|
|
|
|
+}
|
|
|
|
|
+var defaultData = { marker: { color: "rgb(247, 166, 0)" }, hovertemplate: "%{x}: %{y:,.3~s}<extra></extra>" };
|
|
|
|
|
+var defaultLayout = {
|
|
|
|
|
+ title: {
|
|
|
|
|
+ font: { color: "#eee", family: '"Droid Sans", sans-serif' }
|
|
|
|
|
+ },
|
|
|
|
|
+ xaxis: {
|
|
|
|
|
+ title: {
|
|
|
|
|
+ font: { color: "#bbb", family: '"Droid Sans", sans-serif' }
|
|
|
|
|
+ },
|
|
|
|
|
+ tickfont: { color: "#666", family: '"Droid Sans", sans-serif' },
|
|
|
|
|
+ tickangle: -45
|
|
|
|
|
+ },
|
|
|
|
|
+ yaxis: {
|
|
|
|
|
+ title: {
|
|
|
|
|
+ font: { color: "#bbb", family: '"Droid Sans", sans-serif' }
|
|
|
|
|
+ },
|
|
|
|
|
+ tickfont: { color: "#666", family: '"Droid Sans", sans-serif' },
|
|
|
|
|
+ gridcolor: "#323232"
|
|
|
|
|
+ },
|
|
|
|
|
+ plot_bgcolor: "#252525",
|
|
|
|
|
+ paper_bgcolor: "#252525",
|
|
|
|
|
+ dragmode: "pan"
|
|
|
|
|
+};
|
|
|
|
|
+var defaultConfig = {
|
|
|
|
|
+ displayModeBar: true,
|
|
|
|
|
+ modeBarButtonsToRemove: ["lasso2d"],
|
|
|
|
|
+ displaylogo: false,
|
|
|
|
|
+ scrollZoom: true,
|
|
|
|
|
+ responsive: true
|
|
|
|
|
+};
|
|
|
|
|
+function createGraph(plotContainerID, data, layout, config) {
|
|
|
|
|
+ for (var i = 0;i < data.length; i++) {
|
|
|
|
|
+ data[i] = deepMerge(structuredClone(defaultData), data[i]);
|
|
|
|
|
+ }
|
|
|
|
|
+ layout = deepMerge(structuredClone(defaultLayout), layout);
|
|
|
|
|
+ config = deepMerge(structuredClone(defaultConfig), config);
|
|
|
|
|
+ Plotly.newPlot(plotContainerID, { data, layout, config });
|
|
|
|
|
+}
|
|
|
|
|
+function createTable(plotContainerID, titleText, headers, data, dataDisplay) {
|
|
|
|
|
+ const container = document.getElementById(plotContainerID);
|
|
|
|
|
+ if (!container) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ const title = document.createElement("div");
|
|
|
|
|
+ title.textContent = titleText;
|
|
|
|
|
+ title.classList.add("title");
|
|
|
|
|
+ container.appendChild(title);
|
|
|
|
|
+ const table = document.createElement("table");
|
|
|
|
|
+ const header = document.createElement("thead");
|
|
|
|
|
+ const headRow = document.createElement("tr");
|
|
|
|
|
+ headers.forEach((label) => {
|
|
|
|
|
+ const column = document.createElement("th");
|
|
|
|
|
+ column.textContent = label;
|
|
|
|
|
+ headRow.appendChild(column);
|
|
|
|
|
+ });
|
|
|
|
|
+ header.appendChild(headRow);
|
|
|
|
|
+ table.appendChild(header);
|
|
|
|
|
+ const body = document.createElement("tbody");
|
|
|
|
|
+ dataDisplay.forEach((dataRow) => {
|
|
|
|
|
+ const row = document.createElement("tr");
|
|
|
|
|
+ dataRow.forEach((dataElem) => {
|
|
|
|
|
+ const td = document.createElement("td");
|
|
|
|
|
+ td.appendChild(dataElem);
|
|
|
|
|
+ row.appendChild(td);
|
|
|
|
|
+ });
|
|
|
|
|
+ body.appendChild(row);
|
|
|
|
|
+ });
|
|
|
|
|
+ table.appendChild(body);
|
|
|
|
|
+ container.appendChild(table);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// src/graphs/companyHistory.ts
|
|
|
|
|
+class CompanyHistory {
|
|
|
|
|
+ id = "compHistory";
|
|
|
|
|
+ displayName = "Company History";
|
|
|
|
|
+ configFieldIDs = ["metric", "group", "companyName"];
|
|
|
|
|
+ loadedData;
|
|
|
|
|
+ urlParams;
|
|
|
|
|
+ constructor(loadedData, urlParams) {
|
|
|
|
|
+ this.loadedData = loadedData;
|
|
|
|
|
+ this.urlParams = urlParams;
|
|
|
|
|
+ }
|
|
|
|
|
+ setConfigs(useURLParams) {
|
|
|
|
|
+ const updateFunc = function() {
|
|
|
|
|
+ switchPlot();
|
|
|
|
|
+ };
|
|
|
|
|
+ const configDiv = document.getElementById("selectorSubtypes");
|
|
|
|
|
+ if (configDiv) {
|
|
|
|
|
+ clearChildren(configDiv);
|
|
|
|
|
+ }
|
|
|
|
|
+ configDiv?.appendChild(addConfigField("select", "metric", "Metric: ", { prettyValues: ["Volume", "Profit", "Bases"], values: ["volume", "profit", "bases"] }, useURLParams ? this.urlParams.metric : undefined, updateFunc));
|
|
|
|
|
+ configDiv?.appendChild(addConfigField("select", "group", "Group: ", { prettyValues: ["By Company", "By Corporation"], values: ["company", "corp"] }, useURLParams ? this.urlParams.group : undefined, updateUsernameLabel));
|
|
|
|
|
+ configDiv?.appendChild(addConfigField("input", "companyName", "Username: ", undefined, useURLParams ? this.urlParams.companyName : undefined, updateFunc, "-27px"));
|
|
|
|
|
+ }
|
|
|
|
|
+ async generatePlot(configValues, plotContainerID) {
|
|
|
|
|
+ if (!configValues.companyName || configValues.companyName == "") {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ const knownCompanies = await getData(this.loadedData, "knownCompanies");
|
|
|
|
|
+ var companyName;
|
|
|
|
|
+ var companyID;
|
|
|
|
|
+ if (configValues.group == "company") {
|
|
|
|
|
+ companyID = await getCompanyId(configValues.companyName, this.loadedData);
|
|
|
|
|
+ if (!companyID) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ companyName = knownCompanies[companyID]?.Username;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ companyName = configValues.companyName.toUpperCase();
|
|
|
|
|
+ }
|
|
|
|
|
+ const fullCompanyData = [];
|
|
|
|
|
+ for (var i = 0;i < months.length; i++) {
|
|
|
|
|
+ const monthData = await getData(this.loadedData, configValues.metric == "bases" ? "base" : "company", months[i]);
|
|
|
|
|
+ const dataset = configValues.metric == "bases" ? monthData : monthData.totals;
|
|
|
|
|
+ if (configValues.group == "company") {
|
|
|
|
|
+ fullCompanyData.push(dataset[companyID || ""]);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ const corpData = {};
|
|
|
|
|
+ var hasData = false;
|
|
|
|
|
+ const parentCorps = await getData(this.loadedData, "parentCorps");
|
|
|
|
|
+ Object.keys(dataset).forEach((id) => {
|
|
|
|
|
+ const companyObj = knownCompanies[id];
|
|
|
|
|
+ if (companyObj && (companyObj.Corporation == companyName || parentCorps[companyObj.Corporation] == companyName)) {
|
|
|
|
|
+ hasData = true;
|
|
|
|
|
+ Object.keys(dataset[id]).forEach((metric) => {
|
|
|
|
|
+ if (!corpData[metric]) {
|
|
|
|
|
+ corpData[metric] = 0;
|
|
|
|
|
+ }
|
|
|
|
|
+ corpData[metric] += dataset[id][metric];
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+ if (hasData) {
|
|
|
|
|
+ fullCompanyData.push(corpData);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ const validMonths = [];
|
|
|
|
|
+ const companyData = [];
|
|
|
|
|
+ fullCompanyData.forEach((data, i2) => {
|
|
|
|
|
+ if (!data) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ validMonths.push(monthsPretty[i2]);
|
|
|
|
|
+ companyData.push(data[configValues.metric]);
|
|
|
|
|
+ });
|
|
|
|
|
+ const titles = {
|
|
|
|
|
+ profit: "Production Profit History of ",
|
|
|
|
|
+ volume: "Production Volume History of ",
|
|
|
|
|
+ bases: "Base Count History of "
|
|
|
|
|
+ };
|
|
|
|
|
+ const yAxis = {
|
|
|
|
|
+ profit: "Daily Profit [$/day]",
|
|
|
|
|
+ volume: "Daily Volume [$/day]",
|
|
|
|
|
+ bases: "Bases"
|
|
|
|
|
+ };
|
|
|
|
|
+ createGraph(plotContainerID, [{ x: validMonths, y: companyData, type: "bar" }], {
|
|
|
|
|
+ width: this.urlParams.hideOptions !== undefined ? undefined : 800,
|
|
|
|
|
+ height: this.urlParams.hideOptions !== undefined ? undefined : 400,
|
|
|
|
|
+ autosize: this.urlParams.hideOptions !== undefined,
|
|
|
|
|
+ ...this.urlParams.hideOptions !== undefined ? { margin: {
|
|
|
|
|
+ l: 60,
|
|
|
|
|
+ r: 10,
|
|
|
|
|
+ t: 40,
|
|
|
|
|
+ b: 60
|
|
|
|
|
+ } } : {},
|
|
|
|
|
+ title: { text: titles[configValues.metric] + companyName },
|
|
|
|
|
+ xaxis: {
|
|
|
|
|
+ title: { text: "Month" }
|
|
|
|
|
+ },
|
|
|
|
|
+ yaxis: {
|
|
|
|
|
+ title: { text: yAxis[configValues.metric] }
|
|
|
|
|
+ }
|
|
|
|
|
+ }, {});
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// src/graphs/companyRank.ts
|
|
|
|
|
+class CompanyRank {
|
|
|
|
|
+ id = "compRank";
|
|
|
|
|
+ displayName = "Company Rank";
|
|
|
|
|
+ configFieldIDs = ["month", "companyName"];
|
|
|
|
|
+ loadedData;
|
|
|
|
|
+ urlParams;
|
|
|
|
|
+ constructor(loadedData, urlParams) {
|
|
|
|
|
+ this.loadedData = loadedData;
|
|
|
|
|
+ this.urlParams = urlParams;
|
|
|
|
|
+ }
|
|
|
|
|
+ setConfigs(useURLParams) {
|
|
|
|
|
+ const updateFunc = function() {
|
|
|
|
|
+ switchPlot();
|
|
|
|
|
+ };
|
|
|
|
|
+ const configDiv = document.getElementById("selectorSubtypes");
|
|
|
|
|
+ if (configDiv) {
|
|
|
|
|
+ clearChildren(configDiv);
|
|
|
|
|
+ }
|
|
|
|
|
+ configDiv?.appendChild(addConfigField("select", "month", "Month: ", { prettyValues: monthsPretty, values: months }, useURLParams && this.urlParams.month ? this.urlParams.month : months[months.length - 1], updateFunc));
|
|
|
|
|
+ configDiv?.appendChild(addConfigField("input", "companyName", "Username: ", undefined, useURLParams ? this.urlParams.companyName : undefined, updateFunc, "-27px"));
|
|
|
|
|
+ }
|
|
|
|
|
+ async generatePlot(configValues, plotContainerID) {
|
|
|
|
|
+ if (!configValues.companyName || configValues.companyName == "") {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ const knownCompanies = await getData(this.loadedData, "knownCompanies");
|
|
|
|
|
+ var companyID = await getCompanyId(configValues.companyName, this.loadedData);
|
|
|
|
|
+ if (!companyID) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ var companyName = knownCompanies[companyID]?.Username;
|
|
|
|
|
+ const fullCompanyData = await getData(this.loadedData, "company", configValues.month);
|
|
|
|
|
+ const fullPrevCompanyData = configValues.month == months[0] ? { individual: {} } : await getData(this.loadedData, "company", months[months.indexOf(configValues.month) - 1]);
|
|
|
|
|
+ const companyData = fullCompanyData.individual[companyID];
|
|
|
|
|
+ const prevCompanyData = fullPrevCompanyData.individual[companyID];
|
|
|
|
|
+ if (!companyData) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ var tableData = [];
|
|
|
|
|
+ var tableDisplay = [];
|
|
|
|
|
+ Object.keys(companyData).forEach((ticker) => {
|
|
|
|
|
+ const tableRow = [companyData[ticker].rank, ticker, companyData[ticker].amount, companyData[ticker].volume, companyData[ticker].profit];
|
|
|
|
|
+ const tableDisplayRow = [];
|
|
|
|
|
+ if (prevCompanyData) {
|
|
|
|
|
+ const outerRankDiv = document.createElement("div");
|
|
|
|
|
+ const symbolDiv = document.createElement("div");
|
|
|
|
|
+ const rankDiv = document.createElement("div");
|
|
|
|
|
+ outerRankDiv.style.display = "flex";
|
|
|
|
|
+ symbolDiv.style.width = "14px";
|
|
|
|
|
+ symbolDiv.style.minWidth = "14px";
|
|
|
|
|
+ symbolDiv.style.marginRight = "2px";
|
|
|
|
|
+ const prevRank = prevCompanyData[ticker]?.rank;
|
|
|
|
|
+ const increasing = prevRank < companyData[ticker].rank;
|
|
|
|
|
+ if (prevRank && prevRank != companyData[ticker].rank) {
|
|
|
|
|
+ symbolDiv.textContent = increasing ? "▼" : "▲";
|
|
|
|
|
+ symbolDiv.style.color = increasing ? "#d9534f" : "#5cb85c";
|
|
|
|
|
+ }
|
|
|
|
|
+ rankDiv.textContent = companyData[ticker].rank.toLocaleString();
|
|
|
|
|
+ outerRankDiv.appendChild(symbolDiv);
|
|
|
|
|
+ outerRankDiv.appendChild(rankDiv);
|
|
|
|
|
+ tableDisplayRow.push(outerRankDiv);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ const rankDiv = document.createElement("div");
|
|
|
|
|
+ rankDiv.textContent = companyData[ticker].rank.toLocaleString();
|
|
|
|
|
+ tableDisplayRow.push(rankDiv);
|
|
|
|
|
+ }
|
|
|
|
|
+ const tickerDiv = document.createElement("div");
|
|
|
|
|
+ tickerDiv.textContent = ticker;
|
|
|
|
|
+ tableDisplayRow.push(tickerDiv);
|
|
|
|
|
+ const amountDiv = document.createElement("div");
|
|
|
|
|
+ amountDiv.textContent = companyData[ticker].amount.toLocaleString(undefined, { maximumFractionDigits: 1 });
|
|
|
|
|
+ tableDisplayRow.push(amountDiv);
|
|
|
|
|
+ const volumeDiv = document.createElement("div");
|
|
|
|
|
+ volumeDiv.textContent = "$" + companyData[ticker].volume.toLocaleString(undefined, { notation: "compact", maximumSignificantDigits: 3 });
|
|
|
|
|
+ tableDisplayRow.push(volumeDiv);
|
|
|
|
|
+ const profitDiv = document.createElement("div");
|
|
|
|
|
+ const profit = companyData[ticker].profit ?? NaN;
|
|
|
|
|
+ let profitText = "";
|
|
|
|
|
+ if (profit < 0)
|
|
|
|
|
+ profitText = "-$" + (-profit).toLocaleString(undefined, { notation: "compact", maximumSignificantDigits: 3 });
|
|
|
|
|
+ else if (profit > 0)
|
|
|
|
|
+ profitText = "$" + profit.toLocaleString(undefined, { notation: "compact", maximumSignificantDigits: 3 });
|
|
|
|
|
+ profitDiv.textContent = profitText;
|
|
|
|
|
+ tableDisplayRow.push(profitDiv);
|
|
|
|
|
+ tableData.push(tableRow);
|
|
|
|
|
+ tableDisplay.push(tableDisplayRow);
|
|
|
|
|
+ });
|
|
|
|
|
+ const indices = tableData.map((_, i) => i).sort((a, b) => tableData[a][0] - tableData[b][0]);
|
|
|
|
|
+ tableData = indices.map((i) => tableData[i]);
|
|
|
|
|
+ tableDisplay = indices.map((i) => tableDisplay[i]);
|
|
|
|
|
+ const title = "Production Ranking of " + companyName + " - " + prettyMonthName(configValues.month) + `
|
|
|
|
|
+Volume: #` + fullCompanyData.totals[companyID].volumeRank + ", Profit: #" + fullCompanyData.totals[companyID].profitRank;
|
|
|
|
|
+ const headers = ["Rank", "Ticker", "Amount [/day]", "Volume [$/day]", "Profit [$/day]"];
|
|
|
|
|
+ createTable(plotContainerID, title, headers, tableData, tableDisplay);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// src/graphs/companyTotals.ts
|
|
|
|
|
+class CompanyTotals {
|
|
|
|
|
+ id = "compTotals";
|
|
|
|
|
+ displayName = "Company Totals";
|
|
|
|
|
+ configFieldIDs = ["chartType", "metric", "group", "month", "companyName"];
|
|
|
|
|
+ loadedData;
|
|
|
|
|
+ urlParams;
|
|
|
|
|
+ constructor(loadedData, urlParams) {
|
|
|
|
|
+ this.loadedData = loadedData;
|
|
|
|
|
+ this.urlParams = urlParams;
|
|
|
|
|
+ }
|
|
|
|
|
+ setConfigs(useURLParams) {
|
|
|
|
|
+ const updateFunc = function() {
|
|
|
|
|
+ switchPlot();
|
|
|
|
|
+ };
|
|
|
|
|
+ const configDiv = document.getElementById("selectorSubtypes");
|
|
|
|
|
+ if (configDiv) {
|
|
|
|
|
+ clearChildren(configDiv);
|
|
|
|
|
+ }
|
|
|
|
|
+ const usernameLabel = useURLParams && this.urlParams.group && this.urlParams.group != "company" ? "Corp Code: " : "Username: ";
|
|
|
|
|
+ configDiv?.appendChild(addConfigField("select", "chartType", "Chart Type: ", { prettyValues: ["Bar", "Pie", "Treemap (Mat)", "Treemap (Cat)"], values: ["bar", "pie", "treemap", "treemap-categories"] }, useURLParams ? this.urlParams.chartType : "treemap", updateFunc, "-30px"));
|
|
|
|
|
+ configDiv?.appendChild(addConfigField("select", "metric", "Metric: ", { prettyValues: ["Volume", "Profit"], values: ["volume", "profit"] }, useURLParams ? this.urlParams.metric : undefined, updateFunc));
|
|
|
|
|
+ configDiv?.appendChild(addConfigField("select", "group", "Group: ", { prettyValues: ["By Company", "By Corporation"], values: ["company", "corp"] }, useURLParams ? this.urlParams.group : undefined, updateUsernameLabel));
|
|
|
|
|
+ configDiv?.appendChild(addConfigField("select", "month", "Month: ", { prettyValues: monthsPretty, values: months }, useURLParams && this.urlParams.month ? this.urlParams.month : months[months.length - 1], updateFunc));
|
|
|
|
|
+ configDiv?.appendChild(addConfigField("input", "companyName", usernameLabel, undefined, useURLParams ? this.urlParams.companyName : undefined, updateFunc, "-27px"));
|
|
|
|
|
+ }
|
|
|
|
|
+ async generatePlot(configValues, plotContainerID) {
|
|
|
|
|
+ if (!configValues.companyName || configValues.companyName == "") {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ const companyData = await getData(this.loadedData, "company", configValues.month);
|
|
|
|
|
+ const knownCompanies = await getData(this.loadedData, "knownCompanies");
|
|
|
|
|
+ var companyName;
|
|
|
|
|
+ var prodData;
|
|
|
|
|
+ if (configValues.group == "company") {
|
|
|
|
|
+ var companyID = await getCompanyId(configValues.companyName, this.loadedData);
|
|
|
|
|
+ if (!companyID) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ companyName = knownCompanies[companyID]?.Username;
|
|
|
|
|
+ if (!companyData.individual[companyID]) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ prodData = companyData.individual[companyID];
|
|
|
|
|
+ } else {
|
|
|
|
|
+ const parentCorps = await getData(this.loadedData, "parentCorps");
|
|
|
|
|
+ companyName = configValues.companyName.toUpperCase();
|
|
|
|
|
+ prodData = {};
|
|
|
|
|
+ Object.keys(companyData.individual).forEach((id) => {
|
|
|
|
|
+ const companyObj = knownCompanies[id];
|
|
|
|
|
+ if (companyObj && (companyObj.Corporation == companyName || parentCorps[companyObj.Corporation] == companyName)) {
|
|
|
|
|
+ const indivCompanyData = companyData.individual[id];
|
|
|
|
|
+ Object.keys(indivCompanyData).forEach((ticker) => {
|
|
|
|
|
+ if (!prodData[ticker]) {
|
|
|
|
|
+ prodData[ticker] = { volume: 0, profit: 0, amount: 0, rank: 0 };
|
|
|
|
|
+ }
|
|
|
|
|
+ Object.keys(indivCompanyData[ticker]).forEach((metric) => {
|
|
|
|
|
+ prodData[ticker][metric] += indivCompanyData[ticker][metric];
|
|
|
|
|
+ });
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+ if (Object.keys(prodData).length == 0) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ var catData = [];
|
|
|
|
|
+ var categories = [];
|
|
|
|
|
+ var totalValue = 0;
|
|
|
|
|
+ Object.keys(prodData).forEach((ticker) => {
|
|
|
|
|
+ const metric = prodData[ticker][configValues.metric];
|
|
|
|
|
+ if (metric < 0 && (configValues.chartType == "treemap" || configValues.chartType == "treemap-categories")) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ totalValue += metric;
|
|
|
|
|
+ if (configValues.chartType == "treemap-categories") {
|
|
|
|
|
+ const category = getMatCategory(ticker);
|
|
|
|
|
+ const catIndex = categories.indexOf(category);
|
|
|
|
|
+ if (catIndex == -1) {
|
|
|
|
|
+ categories.push(category);
|
|
|
|
|
+ catData.push(metric);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ catData[catIndex] += metric;
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ catData.push(metric);
|
|
|
|
|
+ categories.push(ticker);
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+ const indices = Array.from(categories.keys());
|
|
|
|
|
+ indices.sort((a, b) => catData[b] - catData[a]);
|
|
|
|
|
+ catData = indices.map((i) => catData[i]);
|
|
|
|
|
+ categories = indices.map((i) => categories[i]);
|
|
|
|
|
+ const titles = {
|
|
|
|
|
+ profit: "Production Profit Breakdown of ",
|
|
|
|
|
+ volume: "Production Volume Breakdown of "
|
|
|
|
|
+ };
|
|
|
|
|
+ if (configValues.chartType == "treemap" || configValues.chartType == "treemap-categories") {
|
|
|
|
|
+ const colors = [];
|
|
|
|
|
+ if (configValues.chartType == "treemap") {
|
|
|
|
|
+ categories.forEach((cat) => {
|
|
|
|
|
+ colors.push(getMatColor(cat));
|
|
|
|
|
+ });
|
|
|
|
|
+ } else {
|
|
|
|
|
+ categories.forEach((cat) => {
|
|
|
|
|
+ colors.push(materialCategoryColors[cat] ?? "#000000");
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+ const parents = categories.map((m) => "Total");
|
|
|
|
|
+ categories.push("Total");
|
|
|
|
|
+ catData.push(totalValue);
|
|
|
|
|
+ parents.push("");
|
|
|
|
|
+ colors.push("#252525");
|
|
|
|
|
+ createGraph(plotContainerID, [{
|
|
|
|
|
+ labels: categories,
|
|
|
|
|
+ values: catData,
|
|
|
|
|
+ parents,
|
|
|
|
|
+ type: "treemap",
|
|
|
|
|
+ maxdepth: 2,
|
|
|
|
|
+ branchvalues: "total",
|
|
|
|
|
+ marker: {
|
|
|
|
|
+ colors
|
|
|
|
|
+ },
|
|
|
|
|
+ tiling: {
|
|
|
|
|
+ pad: 0
|
|
|
|
|
+ },
|
|
|
|
|
+ textposition: "middle center",
|
|
|
|
|
+ hovertemplate: "%{label}<br>$%{value:,.3~s}/day<br>%{percentEntry:.2%}<extra></extra>"
|
|
|
|
|
+ }], {
|
|
|
|
|
+ width: this.urlParams.hideOptions !== undefined ? undefined : 800,
|
|
|
|
|
+ height: this.urlParams.hideOptions !== undefined ? undefined : 400,
|
|
|
|
|
+ autosize: this.urlParams.hideOptions !== undefined,
|
|
|
|
|
+ ...this.urlParams.hideOptions !== undefined ? { margin: {
|
|
|
|
|
+ l: 10,
|
|
|
|
|
+ r: 10,
|
|
|
|
|
+ t: 40,
|
|
|
|
|
+ b: 10
|
|
|
|
|
+ } } : {},
|
|
|
|
|
+ title: { text: titles[configValues.metric] + companyName + " - " + prettyMonthName(configValues.month) }
|
|
|
|
|
+ }, {});
|
|
|
|
|
+ } else if (configValues.chartType == "bar") {
|
|
|
|
|
+ createGraph(plotContainerID, [{ x: categories, y: catData, type: "bar" }], {
|
|
|
|
|
+ width: this.urlParams.hideOptions !== undefined ? undefined : 800,
|
|
|
|
|
+ height: this.urlParams.hideOptions !== undefined ? undefined : 400,
|
|
|
|
|
+ autosize: this.urlParams.hideOptions !== undefined,
|
|
|
|
|
+ ...this.urlParams.hideOptions !== undefined ? { margin: {
|
|
|
|
|
+ l: 60,
|
|
|
|
|
+ r: 10,
|
|
|
|
|
+ t: 40,
|
|
|
|
|
+ b: 60
|
|
|
|
|
+ } } : {},
|
|
|
|
|
+ title: { text: titles[configValues.metric] + companyName + " - " + prettyMonthName(configValues.month) },
|
|
|
|
|
+ xaxis: {
|
|
|
|
|
+ title: { text: "Ticker" },
|
|
|
|
|
+ range: [-0.5, Math.min(categories.length, 30) - 0.5]
|
|
|
|
|
+ },
|
|
|
|
|
+ yaxis: {
|
|
|
|
|
+ title: { text: prettyModeNames[configValues.metric] + " [$/day]" },
|
|
|
|
|
+ range: [0, null]
|
|
|
|
|
+ }
|
|
|
|
|
+ }, {});
|
|
|
|
|
+ } else if (configValues.chartType == "pie") {
|
|
|
|
|
+ createGraph(plotContainerID, [{ labels: categories, values: catData, type: "pie", textinfo: "label", textposition: "inside", insidetextorientation: "none", automargin: false, hovertemplate: "%{label}<br>$%{value:,.3~s}/day<br>%{percent}<extra></extra>" }], {
|
|
|
|
|
+ width: this.urlParams.hideOptions !== undefined ? undefined : 800,
|
|
|
|
|
+ height: this.urlParams.hideOptions !== undefined ? undefined : 400,
|
|
|
|
|
+ autosize: this.urlParams.hideOptions !== undefined,
|
|
|
|
|
+ ...this.urlParams.hideOptions !== undefined ? { margin: {
|
|
|
|
|
+ l: 10,
|
|
|
|
|
+ r: 10,
|
|
|
|
|
+ t: 40,
|
|
|
|
|
+ b: 10
|
|
|
|
|
+ } } : {},
|
|
|
|
|
+ title: { text: titles[configValues.metric] + companyName + " - " + prettyMonthName(configValues.month) },
|
|
|
|
|
+ xaxis: {
|
|
|
|
|
+ title: { text: "Ticker" },
|
|
|
|
|
+ range: [-0.5, Math.min(categories.length, 30) - 0.5]
|
|
|
|
|
+ },
|
|
|
|
|
+ yaxis: {
|
|
|
|
|
+ title: { text: prettyModeNames[configValues.metric] + " [$/day]" },
|
|
|
|
|
+ range: [0, null]
|
|
|
|
|
+ }
|
|
|
|
|
+ }, {});
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// src/graphs/matHistory.ts
|
|
|
|
|
+class MatHistory {
|
|
|
|
|
+ id = "matHistory";
|
|
|
|
|
+ displayName = "MAT History";
|
|
|
|
|
+ configFieldIDs = ["metric", "ticker"];
|
|
|
|
|
+ loadedData;
|
|
|
|
|
+ urlParams;
|
|
|
|
|
+ constructor(loadedData, urlParams) {
|
|
|
|
|
+ this.loadedData = loadedData;
|
|
|
|
|
+ this.urlParams = urlParams;
|
|
|
|
|
+ }
|
|
|
|
|
+ setConfigs(useURLParams) {
|
|
|
|
|
+ const updateFunc = function() {
|
|
|
|
|
+ switchPlot();
|
|
|
|
|
+ };
|
|
|
|
|
+ const configDiv = document.getElementById("selectorSubtypes");
|
|
|
|
|
+ if (configDiv) {
|
|
|
|
|
+ clearChildren(configDiv);
|
|
|
|
|
+ }
|
|
|
|
|
+ configDiv?.appendChild(addConfigField("select", "metric", "Metric: ", { prettyValues: ["Volume", "Profit", "Price", "Produced", "Consumption", "Surplus"], values: ["volume", "profit", "price", "amount", "consumed", "surplus"] }, useURLParams ? this.urlParams.metric : undefined, updateFunc));
|
|
|
|
|
+ configDiv?.appendChild(addConfigField("input", "ticker", "Ticker: ", undefined, useURLParams && this.urlParams.ticker ? this.urlParams.ticker : undefined, updateFunc));
|
|
|
|
|
+ }
|
|
|
|
|
+ async generatePlot(configValues, plotContainerID) {
|
|
|
|
|
+ if (!configValues.ticker || configValues.ticker == "") {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ const totalTickerData = [];
|
|
|
|
|
+ for (var i = 0;i < months.length; i++) {
|
|
|
|
|
+ const monthData = await getData(this.loadedData, "prod", months[i]);
|
|
|
|
|
+ totalTickerData.push(monthData[(configValues.ticker ?? "").toUpperCase()]);
|
|
|
|
|
+ }
|
|
|
|
|
+ const tickerData = [];
|
|
|
|
|
+ const validMonths = [];
|
|
|
|
|
+ totalTickerData.forEach((data, i2) => {
|
|
|
|
|
+ if (!data) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ validMonths.push(monthsPretty[i2]);
|
|
|
|
|
+ switch (configValues.metric) {
|
|
|
|
|
+ case "volume":
|
|
|
|
|
+ tickerData.push(data.volume);
|
|
|
|
|
+ break;
|
|
|
|
|
+ case "profit":
|
|
|
|
|
+ tickerData.push(data.profit);
|
|
|
|
|
+ break;
|
|
|
|
|
+ case "price":
|
|
|
|
|
+ tickerData.push(data.amount == 0 ? 0 : data.volume / data.amount);
|
|
|
|
|
+ break;
|
|
|
|
|
+ case "amount":
|
|
|
|
|
+ tickerData.push(data.amount);
|
|
|
|
|
+ break;
|
|
|
|
|
+ case "consumed":
|
|
|
|
|
+ tickerData.push(data.consumed);
|
|
|
|
|
+ break;
|
|
|
|
|
+ case "surplus":
|
|
|
|
|
+ tickerData.push(data.amount - data.consumed);
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+ if (validMonths.length == 0) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ const titles = {
|
|
|
|
|
+ profit: "Production Profit History of ",
|
|
|
|
|
+ volume: "Production Volume History of ",
|
|
|
|
|
+ amount: "Production Amount History of ",
|
|
|
|
|
+ price: "Price History of ",
|
|
|
|
|
+ consumed: "Consumption History of ",
|
|
|
|
|
+ surplus: "Surplus Production History of "
|
|
|
|
|
+ };
|
|
|
|
|
+ const yAxis = {
|
|
|
|
|
+ profit: "Daily Profit [$/day]",
|
|
|
|
|
+ volume: "Daily Volume [$/day]",
|
|
|
|
|
+ amount: "Daily Production [per day]",
|
|
|
|
|
+ price: "Price [$]",
|
|
|
|
|
+ consumed: "Daily Consumption [per day]",
|
|
|
|
|
+ surplus: "Daily Surplus [per day]"
|
|
|
|
|
+ };
|
|
|
|
|
+ createGraph(plotContainerID, [{ x: validMonths, y: tickerData, type: "bar" }], {
|
|
|
|
|
+ width: this.urlParams.hideOptions !== undefined ? undefined : 800,
|
|
|
|
|
+ height: this.urlParams.hideOptions !== undefined ? undefined : 400,
|
|
|
|
|
+ autosize: this.urlParams.hideOptions !== undefined,
|
|
|
|
|
+ ...this.urlParams.hideOptions !== undefined ? { margin: {
|
|
|
|
|
+ l: 60,
|
|
|
|
|
+ r: 10,
|
|
|
|
|
+ t: 40,
|
|
|
|
|
+ b: 60
|
|
|
|
|
+ } } : {},
|
|
|
|
|
+ title: { text: titles[configValues.metric] + (configValues.ticker ?? "").toUpperCase() },
|
|
|
|
|
+ xaxis: {
|
|
|
|
|
+ title: { text: "Month" }
|
|
|
|
|
+ },
|
|
|
|
|
+ yaxis: {
|
|
|
|
|
+ title: { text: yAxis[configValues.metric] }
|
|
|
|
|
+ }
|
|
|
|
|
+ }, {});
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// src/graphs/topCompanies.ts
|
|
|
|
|
+class TopCompanies {
|
|
|
|
|
+ id = "topCompanies";
|
|
|
|
|
+ displayName = "Top Companies";
|
|
|
|
|
+ configFieldIDs = ["metric", "group", "month"];
|
|
|
|
|
+ loadedData;
|
|
|
|
|
+ urlParams;
|
|
|
|
|
+ constructor(loadedData, urlParams) {
|
|
|
|
|
+ this.loadedData = loadedData;
|
|
|
|
|
+ this.urlParams = urlParams;
|
|
|
|
|
+ }
|
|
|
|
|
+ setConfigs(useURLParams) {
|
|
|
|
|
+ const updateFunc = function() {
|
|
|
|
|
+ switchPlot();
|
|
|
|
|
+ };
|
|
|
|
|
+ const configDiv = document.getElementById("selectorSubtypes");
|
|
|
|
|
+ if (configDiv) {
|
|
|
|
|
+ clearChildren(configDiv);
|
|
|
|
|
+ }
|
|
|
|
|
+ configDiv?.appendChild(addConfigField("select", "metric", "Metric: ", { prettyValues: ["Volume", "Profit", "Bases", "Ships"], values: ["volume", "profit", "bases", "ships"] }, useURLParams ? this.urlParams.metric : undefined, updateFunc));
|
|
|
|
|
+ configDiv?.appendChild(addConfigField("select", "group", "Group: ", { prettyValues: ["By Company", "By Corporation"], values: ["company", "corp"] }, useURLParams ? this.urlParams.group : undefined, updateFunc));
|
|
|
|
|
+ configDiv?.appendChild(addConfigField("select", "month", "Month: ", { prettyValues: monthsPretty, values: months }, useURLParams && this.urlParams.month ? this.urlParams.month : months[months.length - 1], updateFunc));
|
|
|
|
|
+ }
|
|
|
|
|
+ async generatePlot(configValues, plotContainerID) {
|
|
|
|
|
+ var fileName;
|
|
|
|
|
+ switch (configValues.metric) {
|
|
|
|
|
+ case "bases":
|
|
|
|
|
+ fileName = "base";
|
|
|
|
|
+ break;
|
|
|
|
|
+ case "ships":
|
|
|
|
|
+ fileName = "ship";
|
|
|
|
|
+ break;
|
|
|
|
|
+ default:
|
|
|
|
|
+ fileName = "company";
|
|
|
|
|
+ }
|
|
|
|
|
+ const companyData = await getData(this.loadedData, fileName, configValues.month);
|
|
|
|
|
+ const knownCompanies = await getData(this.loadedData, "knownCompanies");
|
|
|
|
|
+ const dataset = configValues.metric == "bases" || configValues.metric == "ships" ? companyData : companyData.totals;
|
|
|
|
|
+ var companyNames;
|
|
|
|
|
+ var volumes;
|
|
|
|
|
+ if (!companyData) {
|
|
|
|
|
+ console.error("Data type not defined for this month.");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (configValues.group == "corp") {
|
|
|
|
|
+ const parentCorps = await getData(this.loadedData, "parentCorps");
|
|
|
|
|
+ const corpData = {};
|
|
|
|
|
+ Object.keys(dataset).forEach((id) => {
|
|
|
|
|
+ const companyObj = knownCompanies[id];
|
|
|
|
|
+ if (companyObj && companyObj["Corporation"]) {
|
|
|
|
|
+ var corp = companyObj["Corporation"];
|
|
|
|
|
+ if (parentCorps[corp]) {
|
|
|
|
|
+ corp = parentCorps[corp];
|
|
|
|
|
+ }
|
|
|
|
|
+ if (corpData[corp]) {
|
|
|
|
|
+ corpData[corp] += dataset[id][configValues.metric];
|
|
|
|
|
+ } else {
|
|
|
|
|
+ corpData[corp] = dataset[id][configValues.metric];
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+ const volumeArray = Object.entries(corpData).map(([corpCode, info]) => ({
|
|
|
|
|
+ corpCode,
|
|
|
|
|
+ volume: info
|
|
|
|
|
+ }));
|
|
|
|
|
+ volumeArray.sort((a, b) => b.volume - a.volume);
|
|
|
|
|
+ companyNames = volumeArray.map((item) => item.corpCode);
|
|
|
|
|
+ volumes = volumeArray.map((item) => item.volume);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ const volumeArray = Object.entries(dataset).map(([companyID, info]) => ({
|
|
|
|
|
+ companyID,
|
|
|
|
|
+ volume: info[configValues.metric]
|
|
|
|
|
+ }));
|
|
|
|
|
+ volumeArray.sort((a, b) => b.volume - a.volume);
|
|
|
|
|
+ const companyIDs = volumeArray.map((item) => item.companyID);
|
|
|
|
|
+ volumes = volumeArray.map((item) => item.volume);
|
|
|
|
|
+ companyNames = [];
|
|
|
|
|
+ companyIDs.forEach((id) => {
|
|
|
|
|
+ const companyObj = knownCompanies[id];
|
|
|
|
|
+ companyNames.push(companyObj ? companyObj["Username"] : id.slice(0, 5) + "...");
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+ const prettyGroupNames = { company: "Companies", corp: "Corporations" };
|
|
|
|
|
+ createGraph(plotContainerID, [{ x: companyNames, y: volumes, type: "bar" }], {
|
|
|
|
|
+ width: this.urlParams.hideOptions !== undefined ? undefined : 800,
|
|
|
|
|
+ height: this.urlParams.hideOptions !== undefined ? undefined : 400,
|
|
|
|
|
+ autosize: this.urlParams.hideOptions !== undefined,
|
|
|
|
|
+ ...this.urlParams.hideOptions !== undefined ? { margin: {
|
|
|
|
|
+ l: 60,
|
|
|
|
|
+ r: 10,
|
|
|
|
|
+ t: 40,
|
|
|
|
|
+ b: 100
|
|
|
|
|
+ } } : {},
|
|
|
|
|
+ title: { text: "Top " + prettyGroupNames[configValues.group] + " (" + prettyModeNames[configValues.metric] + ") - " + prettyMonthName(configValues.month) },
|
|
|
|
|
+ xaxis: {
|
|
|
|
|
+ title: { text: "Ticker" },
|
|
|
|
|
+ range: [-0.5, 29.5]
|
|
|
|
|
+ },
|
|
|
|
|
+ yaxis: {
|
|
|
|
|
+ title: { text: prettyModeNames[configValues.metric] + (configValues.metric == "bases" || configValues.metric == "ships" ? "" : " [$/day]") },
|
|
|
|
|
+ range: [0, null]
|
|
|
|
|
+ }
|
|
|
|
|
+ }, {});
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// src/graphs/topProduction.ts
|
|
|
|
|
+class TopProduction {
|
|
|
|
|
+ id = "topProduction";
|
|
|
|
|
+ displayName = "Top Production";
|
|
|
|
|
+ configFieldIDs = ["metric", "month"];
|
|
|
|
|
+ loadedData;
|
|
|
|
|
+ urlParams;
|
|
|
|
|
+ constructor(loadedData, urlParams) {
|
|
|
|
|
+ this.loadedData = loadedData;
|
|
|
|
|
+ this.urlParams = urlParams;
|
|
|
|
|
+ }
|
|
|
|
|
+ setConfigs(useURLParams) {
|
|
|
|
|
+ const updateFunc = function() {
|
|
|
|
|
+ switchPlot();
|
|
|
|
|
+ };
|
|
|
|
|
+ const configDiv = document.getElementById("selectorSubtypes");
|
|
|
|
|
+ if (configDiv) {
|
|
|
|
|
+ clearChildren(configDiv);
|
|
|
|
|
+ }
|
|
|
|
|
+ configDiv?.appendChild(addConfigField("select", "metric", "Metric: ", { prettyValues: ["Volume", "Profit", "Deficit"], values: ["volume", "profit", "deficit"] }, useURLParams ? this.urlParams.metric : undefined, updateFunc));
|
|
|
|
|
+ configDiv?.appendChild(addConfigField("select", "month", "Month: ", { prettyValues: monthsPretty, values: months }, useURLParams && this.urlParams.month ? this.urlParams.month : months[months.length - 1], updateFunc));
|
|
|
|
|
+ }
|
|
|
|
|
+ async generatePlot(configValues, plotContainerID) {
|
|
|
|
|
+ const prodData = await getData(this.loadedData, "prod", configValues.month);
|
|
|
|
|
+ if (configValues.metric == "deficit") {
|
|
|
|
|
+ Object.keys(prodData).forEach((ticker) => {
|
|
|
|
|
+ if (!prodData[ticker].amount || prodData[ticker].amount == 0) {
|
|
|
|
|
+ prodData[ticker].deficit = 0;
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ prodData[ticker].deficit = (prodData[ticker].amount - (prodData[ticker].consumed || 0)) * prodData[ticker].volume / prodData[ticker].amount;
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+ const titles = {
|
|
|
|
|
+ profit: "Profit Materials",
|
|
|
|
|
+ volume: "Production Volumes",
|
|
|
|
|
+ deficit: "Deficits"
|
|
|
|
|
+ };
|
|
|
|
|
+ const volumeArray = Object.entries(prodData).map(([ticker, info]) => ({
|
|
|
|
|
+ ticker,
|
|
|
|
|
+ volume: info[configValues.metric]
|
|
|
|
|
+ }));
|
|
|
|
|
+ if (configValues.metric == "deficit") {
|
|
|
|
|
+ volumeArray.sort((a, b) => a.volume - b.volume);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ volumeArray.sort((a, b) => b.volume - a.volume);
|
|
|
|
|
+ }
|
|
|
|
|
+ const tickers = volumeArray.map((item) => item.ticker);
|
|
|
|
|
+ const volumes = volumeArray.map((item) => item.volume);
|
|
|
|
|
+ createGraph(plotContainerID, [{ x: tickers, y: volumes, type: "bar" }], {
|
|
|
|
|
+ width: this.urlParams.hideOptions !== undefined ? undefined : 800,
|
|
|
|
|
+ height: this.urlParams.hideOptions !== undefined ? undefined : 400,
|
|
|
|
|
+ autosize: this.urlParams.hideOptions !== undefined,
|
|
|
|
|
+ ...this.urlParams.hideOptions !== undefined ? { margin: {
|
|
|
|
|
+ l: 60,
|
|
|
|
|
+ r: 10,
|
|
|
|
|
+ t: 40,
|
|
|
|
|
+ b: 60
|
|
|
|
|
+ } } : {},
|
|
|
|
|
+ title: { text: "Top " + titles[configValues.metric] + " - " + prettyMonthName(configValues.month) },
|
|
|
|
|
+ xaxis: {
|
|
|
|
|
+ title: { text: "Ticker" },
|
|
|
|
|
+ range: [-0.5, 29.5]
|
|
|
|
|
+ },
|
|
|
|
|
+ yaxis: {
|
|
|
|
|
+ title: { text: prettyModeNames[configValues.metric] + " [$/day]" },
|
|
|
|
|
+ range: [configValues.metric == "deficit" ? null : 0, configValues.metric == "deficit" ? 0 : null]
|
|
|
|
|
+ }
|
|
|
|
|
+ }, {});
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// src/graphs/universeHistory.ts
|
|
|
|
|
+class UniverseHistory {
|
|
|
|
|
+ id = "universeHistory";
|
|
|
|
|
+ displayName = "Universe History";
|
|
|
|
|
+ configFieldIDs = ["metric"];
|
|
|
|
|
+ loadedData;
|
|
|
|
|
+ urlParams;
|
|
|
|
|
+ constructor(loadedData, urlParams) {
|
|
|
|
|
+ this.loadedData = loadedData;
|
|
|
|
|
+ this.urlParams = urlParams;
|
|
|
|
|
+ }
|
|
|
|
|
+ setConfigs(useURLParams) {
|
|
|
|
|
+ const updateFunc = function() {
|
|
|
|
|
+ switchPlot();
|
|
|
|
|
+ };
|
|
|
|
|
+ const configDiv = document.getElementById("selectorSubtypes");
|
|
|
|
|
+ if (configDiv) {
|
|
|
|
|
+ clearChildren(configDiv);
|
|
|
|
|
+ }
|
|
|
|
|
+ configDiv?.appendChild(addConfigField("select", "metric", "Metric: ", { prettyValues: ["Volume", "Profit", "Bases", "Companies"], values: ["volume", "profit", "bases", "companies"] }, useURLParams ? this.urlParams.metric : undefined, updateFunc));
|
|
|
|
|
+ }
|
|
|
|
|
+ async generatePlot(configValues, plotContainerID) {
|
|
|
|
|
+ const data = [];
|
|
|
|
|
+ const uniData = await getData(this.loadedData, "universe");
|
|
|
|
|
+ for (var i = 0;i < months.length; i++) {
|
|
|
|
|
+ data.push(uniData[months[i]][configValues.metric]);
|
|
|
|
|
+ }
|
|
|
|
|
+ const titles = {
|
|
|
|
|
+ profit: "Profit History of the Universe",
|
|
|
|
|
+ volume: "Production Volume History of the Universe",
|
|
|
|
|
+ bases: "Base Count History of the Universe",
|
|
|
|
|
+ companies: "Company Count History of the Universe"
|
|
|
|
|
+ };
|
|
|
|
|
+ const yAxis = {
|
|
|
|
|
+ profit: "Daily Profit [$/day]",
|
|
|
|
|
+ volume: "Daily Volume [$/day]",
|
|
|
|
|
+ bases: "Bases",
|
|
|
|
|
+ companies: "Companies"
|
|
|
|
|
+ };
|
|
|
|
|
+ createGraph(plotContainerID, [{ x: monthsPretty, y: data, type: "bar" }], {
|
|
|
|
|
+ width: this.urlParams.hideOptions !== undefined ? undefined : 800,
|
|
|
|
|
+ height: this.urlParams.hideOptions !== undefined ? undefined : 400,
|
|
|
|
|
+ autosize: this.urlParams.hideOptions !== undefined,
|
|
|
|
|
+ ...this.urlParams.hideOptions !== undefined ? { margin: {
|
|
|
|
|
+ l: 60,
|
|
|
|
|
+ r: 10,
|
|
|
|
|
+ t: 40,
|
|
|
|
|
+ b: 60
|
|
|
|
|
+ } } : {},
|
|
|
|
|
+ title: { text: titles[configValues.metric] },
|
|
|
|
|
+ xaxis: {
|
|
|
|
|
+ title: { text: "Month" }
|
|
|
|
|
+ },
|
|
|
|
|
+ yaxis: {
|
|
|
|
|
+ title: { text: yAxis[configValues.metric] }
|
|
|
|
|
+ }
|
|
|
|
|
+ }, {});
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// src/graphs/marketOverview.ts
|
|
|
|
|
+class MarketOverview {
|
|
|
|
|
+ id = "marketOverview";
|
|
|
|
|
+ displayName = "Market Overview";
|
|
|
|
|
+ configFieldIDs = ["month", "ticker"];
|
|
|
|
|
+ loadedData;
|
|
|
|
|
+ urlParams;
|
|
|
|
|
+ constructor(loadedData, urlParams) {
|
|
|
|
|
+ this.loadedData = loadedData;
|
|
|
|
|
+ this.urlParams = urlParams;
|
|
|
|
|
+ }
|
|
|
|
|
+ setConfigs(useURLParams) {
|
|
|
|
|
+ const updateFunc = switchPlot;
|
|
|
|
|
+ const configDiv = document.getElementById("selectorSubtypes");
|
|
|
|
|
+ if (configDiv) {
|
|
|
|
|
+ clearChildren(configDiv);
|
|
|
|
|
+ }
|
|
|
|
|
+ configDiv?.appendChild(addConfigField("select", "month", "Month: ", {
|
|
|
|
|
+ prettyValues: monthsPretty,
|
|
|
|
|
+ values: months
|
|
|
|
|
+ }, useURLParams && this.urlParams.month ? this.urlParams.month : months[months.length - 1], updateFunc));
|
|
|
|
|
+ configDiv?.appendChild(addConfigField("input", "ticker", "Ticker: ", undefined, useURLParams && this.urlParams.ticker ? this.urlParams.ticker : undefined, updateFunc));
|
|
|
|
|
+ }
|
|
|
|
|
+ async generatePlot(configValues, plotContainerID) {
|
|
|
|
|
+ const ticker = configValues.ticker?.toUpperCase();
|
|
|
|
|
+ if (!ticker) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ const companyData = await getData(this.loadedData, "company", configValues.month);
|
|
|
|
|
+ const knownCompanies = await getData(this.loadedData, "knownCompanies");
|
|
|
|
|
+ const labels = [];
|
|
|
|
|
+ const parents = [];
|
|
|
|
|
+ const values = [];
|
|
|
|
|
+ let totalAmount = 0;
|
|
|
|
|
+ let totalVolume = 0;
|
|
|
|
|
+ let totalProfit = 0;
|
|
|
|
|
+ for (const key of Object.keys(companyData.individual)) {
|
|
|
|
|
+ const individualData = companyData.individual[key];
|
|
|
|
|
+ const tickerData = individualData[ticker];
|
|
|
|
|
+ if (!tickerData) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+ const companyObj = knownCompanies[key];
|
|
|
|
|
+ labels.push(companyObj ? companyObj["Username"] : key.slice(0, 5) + "...");
|
|
|
|
|
+ parents.push("Total");
|
|
|
|
|
+ values.push(tickerData.amount);
|
|
|
|
|
+ totalVolume += tickerData.volume;
|
|
|
|
|
+ totalProfit += tickerData.profit;
|
|
|
|
|
+ totalAmount += tickerData.amount;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (labels.length === 0) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ labels.push("Total");
|
|
|
|
|
+ parents.push("");
|
|
|
|
|
+ values.push(totalAmount);
|
|
|
|
|
+ const formatMoney = (num) => "$" + num.toLocaleString(undefined, { maximumFractionDigits: 0 });
|
|
|
|
|
+ const title = `${ticker} Market - ${prettyMonthName(configValues.month)}` + "<br>" + `Produced per day: ${Math.round(totalAmount).toLocaleString()} ${ticker}` + "<br>" + `Volume: ${formatMoney(totalVolume)} | Profit: ${formatMoney(totalProfit)}`;
|
|
|
|
|
+ createGraph(plotContainerID, [{
|
|
|
|
|
+ labels,
|
|
|
|
|
+ values,
|
|
|
|
|
+ parents,
|
|
|
|
|
+ type: "treemap",
|
|
|
|
|
+ branchvalues: "total",
|
|
|
|
|
+ tiling: {
|
|
|
|
|
+ pad: 0
|
|
|
|
|
+ },
|
|
|
|
|
+ textposition: "middle center",
|
|
|
|
|
+ hovertemplate: "%{label}<br>%{value:,.3~s}/day<br>%{percentEntry:.2%}<extra></extra>"
|
|
|
|
|
+ }], {
|
|
|
|
|
+ title: { text: title },
|
|
|
|
|
+ width: this.urlParams.hideOptions !== undefined ? undefined : 800,
|
|
|
|
|
+ height: this.urlParams.hideOptions !== undefined ? undefined : 400,
|
|
|
|
|
+ autosize: this.urlParams.hideOptions !== undefined,
|
|
|
|
|
+ ...this.urlParams.hideOptions !== undefined ? {
|
|
|
|
|
+ margin: {
|
|
|
|
|
+ l: 10,
|
|
|
|
|
+ r: 10,
|
|
|
|
|
+ t: 60,
|
|
|
|
|
+ b: 10
|
|
|
|
|
+ }
|
|
|
|
|
+ } : {}
|
|
|
|
|
+ }, {});
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// src/graphs/corpBreakdown.ts
|
|
|
|
|
+class CorporationBreakdown {
|
|
|
|
|
+ id = "corpBreakdown";
|
|
|
|
|
+ displayName = "Corp Breakdown";
|
|
|
|
|
+ configFieldIDs = ["chartType", "metric", "month", "companyName"];
|
|
|
|
|
+ loadedData;
|
|
|
|
|
+ urlParams;
|
|
|
|
|
+ constructor(loadedData, urlParams) {
|
|
|
|
|
+ this.loadedData = loadedData;
|
|
|
|
|
+ this.urlParams = urlParams;
|
|
|
|
|
+ }
|
|
|
|
|
+ setConfigs(useURLParams) {
|
|
|
|
|
+ const updateFunc = function() {
|
|
|
|
|
+ switchPlot();
|
|
|
|
|
+ };
|
|
|
|
|
+ const configDiv = document.getElementById("selectorSubtypes");
|
|
|
|
|
+ if (configDiv) {
|
|
|
|
|
+ clearChildren(configDiv);
|
|
|
|
|
+ }
|
|
|
|
|
+ configDiv?.appendChild(addConfigField("select", "chartType", "Chart Type: ", { prettyValues: ["Bar", "Pie", "Treemap"], values: ["bar", "pie", "treemap"] }, useURLParams ? this.urlParams.chartType : "treemap", updateFunc, "-30px"));
|
|
|
|
|
+ configDiv?.appendChild(addConfigField("select", "metric", "Metric: ", { prettyValues: ["Volume", "Profit", "Bases", "Ships"], values: ["volume", "profit", "bases", "ships"] }, useURLParams ? this.urlParams.metric : undefined, updateFunc));
|
|
|
|
|
+ configDiv?.appendChild(addConfigField("select", "month", "Month: ", { prettyValues: monthsPretty, values: months }, useURLParams && this.urlParams.month ? this.urlParams.month : months[months.length - 1], updateFunc));
|
|
|
|
|
+ configDiv?.appendChild(addConfigField("input", "companyName", "Corp Code: ", undefined, useURLParams ? this.urlParams.companyName : undefined, updateFunc, "-29px"));
|
|
|
|
|
+ }
|
|
|
|
|
+ async generatePlot(configValues, plotContainerID) {
|
|
|
|
|
+ if (!configValues.companyName || configValues.companyName == "") {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ var fileName;
|
|
|
|
|
+ switch (configValues.metric) {
|
|
|
|
|
+ case "bases":
|
|
|
|
|
+ fileName = "base";
|
|
|
|
|
+ break;
|
|
|
|
|
+ case "ships":
|
|
|
|
|
+ fileName = "ship";
|
|
|
|
|
+ break;
|
|
|
|
|
+ default:
|
|
|
|
|
+ fileName = "company";
|
|
|
|
|
+ }
|
|
|
|
|
+ const companyData = await getData(this.loadedData, fileName, configValues.month);
|
|
|
|
|
+ const dataset = configValues.metric == "bases" || configValues.metric == "ships" ? companyData : companyData.totals;
|
|
|
|
|
+ const knownCompanies = await getData(this.loadedData, "knownCompanies");
|
|
|
|
|
+ var companyName;
|
|
|
|
|
+ const parentCorps = await getData(this.loadedData, "parentCorps");
|
|
|
|
|
+ companyName = configValues.companyName.toUpperCase();
|
|
|
|
|
+ const corpData = {};
|
|
|
|
|
+ if (!companyData) {
|
|
|
|
|
+ console.error("Data type not defined for this month.");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ Object.keys(dataset).forEach((id) => {
|
|
|
|
|
+ const companyObj = knownCompanies[id];
|
|
|
|
|
+ if (companyObj && (companyObj.Corporation == companyName || parentCorps[companyObj.Corporation] == companyName)) {
|
|
|
|
|
+ const indivCompanyData = dataset[id];
|
|
|
|
|
+ const name = companyObj["Username"];
|
|
|
|
|
+ corpData[name] = indivCompanyData;
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+ if (Object.keys(corpData).length == 0) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ var catData = [];
|
|
|
|
|
+ var categories = [];
|
|
|
|
|
+ var totalValue = 0;
|
|
|
|
|
+ Object.keys(corpData).forEach((name) => {
|
|
|
|
|
+ const metric = corpData[name][configValues.metric];
|
|
|
|
|
+ if (metric < 0 && configValues.chartType == "treemap") {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ totalValue += metric;
|
|
|
|
|
+ catData.push(metric);
|
|
|
|
|
+ categories.push(name);
|
|
|
|
|
+ });
|
|
|
|
|
+ const indices = Array.from(categories.keys());
|
|
|
|
|
+ indices.sort((a, b) => catData[b] - catData[a]);
|
|
|
|
|
+ catData = indices.map((i) => catData[i]);
|
|
|
|
|
+ categories = indices.map((i) => categories[i]);
|
|
|
|
|
+ const titles = {
|
|
|
|
|
+ profit: "Production Profit Breakdown of ",
|
|
|
|
|
+ volume: "Production Volume Breakdown of ",
|
|
|
|
|
+ bases: "Base Breakdown of ",
|
|
|
|
|
+ ships: "Ship Breakdown of "
|
|
|
|
|
+ };
|
|
|
|
|
+ const hoverTemplates = {
|
|
|
|
|
+ profit: "%{label}<br>$%{value:,.3~s}/day<br>%{percentEntry:.2%}<extra></extra>",
|
|
|
|
|
+ volume: "%{label}<br>$%{value:,.3~s}/day<br>%{percentEntry:.2%}<extra></extra>",
|
|
|
|
|
+ bases: "%{label}<br>%{value:,.3~s}<br>%{percentEntry:.2%}<extra></extra>",
|
|
|
|
|
+ ships: "%{label}<br>%{value:,.3~s}<br>%{percentEntry:.2%}<extra></extra>"
|
|
|
|
|
+ };
|
|
|
|
|
+ if (configValues.chartType == "treemap") {
|
|
|
|
|
+ const parents = categories.map((m) => "Total");
|
|
|
|
|
+ categories.push("Total");
|
|
|
|
|
+ catData.push(totalValue);
|
|
|
|
|
+ parents.push("");
|
|
|
|
|
+ createGraph(plotContainerID, [{
|
|
|
|
|
+ labels: categories,
|
|
|
|
|
+ values: catData,
|
|
|
|
|
+ parents,
|
|
|
|
|
+ type: "treemap",
|
|
|
|
|
+ maxdepth: 2,
|
|
|
|
|
+ branchvalues: "total",
|
|
|
|
|
+ tiling: {
|
|
|
|
|
+ pad: 0
|
|
|
|
|
+ },
|
|
|
|
|
+ textposition: "middle center",
|
|
|
|
|
+ hovertemplate: hoverTemplates[configValues.metric]
|
|
|
|
|
+ }], {
|
|
|
|
|
+ width: this.urlParams.hideOptions !== undefined ? undefined : 800,
|
|
|
|
|
+ height: this.urlParams.hideOptions !== undefined ? undefined : 400,
|
|
|
|
|
+ autosize: this.urlParams.hideOptions !== undefined,
|
|
|
|
|
+ ...this.urlParams.hideOptions !== undefined ? { margin: {
|
|
|
|
|
+ l: 10,
|
|
|
|
|
+ r: 10,
|
|
|
|
|
+ t: 40,
|
|
|
|
|
+ b: 10
|
|
|
|
|
+ } } : {},
|
|
|
|
|
+ title: { text: titles[configValues.metric] + companyName + " - " + prettyMonthName(configValues.month) }
|
|
|
|
|
+ }, {});
|
|
|
|
|
+ } else if (configValues.chartType == "bar") {
|
|
|
|
|
+ createGraph(plotContainerID, [{ x: categories, y: catData, type: "bar" }], {
|
|
|
|
|
+ width: this.urlParams.hideOptions !== undefined ? undefined : 800,
|
|
|
|
|
+ height: this.urlParams.hideOptions !== undefined ? undefined : 400,
|
|
|
|
|
+ autosize: this.urlParams.hideOptions !== undefined,
|
|
|
|
|
+ ...this.urlParams.hideOptions !== undefined ? { margin: {
|
|
|
|
|
+ l: 60,
|
|
|
|
|
+ r: 10,
|
|
|
|
|
+ t: 40,
|
|
|
|
|
+ b: 60
|
|
|
|
|
+ } } : {},
|
|
|
|
|
+ title: { text: titles[configValues.metric] + companyName + " - " + prettyMonthName(configValues.month) },
|
|
|
|
|
+ xaxis: {
|
|
|
|
|
+ title: { text: "Ticker" },
|
|
|
|
|
+ range: [-0.5, Math.min(categories.length, 30) - 0.5]
|
|
|
|
|
+ },
|
|
|
|
|
+ yaxis: {
|
|
|
|
|
+ title: { text: prettyModeNames[configValues.metric] + " [$/day]" },
|
|
|
|
|
+ range: [0, null]
|
|
|
|
|
+ }
|
|
|
|
|
+ }, {});
|
|
|
|
|
+ } else if (configValues.chartType == "pie") {
|
|
|
|
|
+ createGraph(plotContainerID, [{ labels: categories, values: catData, type: "pie", textinfo: "label", textposition: "inside", insidetextorientation: "none", automargin: false, hovertemplate: "%{label}<br>$%{value:,.3~s}/day<br>%{percent}<extra></extra>" }], {
|
|
|
|
|
+ width: this.urlParams.hideOptions !== undefined ? undefined : 800,
|
|
|
|
|
+ height: this.urlParams.hideOptions !== undefined ? undefined : 400,
|
|
|
|
|
+ autosize: this.urlParams.hideOptions !== undefined,
|
|
|
|
|
+ ...this.urlParams.hideOptions !== undefined ? { margin: {
|
|
|
|
|
+ l: 10,
|
|
|
|
|
+ r: 10,
|
|
|
|
|
+ t: 40,
|
|
|
|
|
+ b: 10
|
|
|
|
|
+ } } : {},
|
|
|
|
|
+ title: { text: titles[configValues.metric] + companyName + " - " + prettyMonthName(configValues.month) },
|
|
|
|
|
+ xaxis: {
|
|
|
|
|
+ title: { text: "Ticker" },
|
|
|
|
|
+ range: [-0.5, Math.min(categories.length, 30) - 0.5]
|
|
|
|
|
+ },
|
|
|
|
|
+ yaxis: {
|
|
|
|
|
+ title: { text: prettyModeNames[configValues.metric] + " [$/day]" },
|
|
|
|
|
+ range: [0, null]
|
|
|
|
|
+ }
|
|
|
|
|
+ }, {});
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// src/main.ts
|
|
|
|
|
+window.onload = function() {
|
|
|
|
|
+ addPermalink();
|
|
|
|
|
+ const graphSelect = document.getElementById("graphType");
|
|
|
|
|
+ graphs.forEach((graph) => {
|
|
|
|
|
+ addOption(graphSelect, graph.displayName, graph.id);
|
|
|
|
|
+ });
|
|
|
|
|
+ if (urlParams.type) {
|
|
|
|
|
+ graphSelect.value = urlParams.type;
|
|
|
|
|
+ }
|
|
|
|
|
+ graphSelect.addEventListener("change", function() {
|
|
|
|
|
+ graphs.find((graph) => graph.id == graphSelect.value)?.setConfigs();
|
|
|
|
|
+ switchPlot();
|
|
|
|
|
+ });
|
|
|
|
|
+ graphs.find((graph) => graph.id == graphSelect.value)?.setConfigs(true);
|
|
|
|
|
+ switchPlot();
|
|
|
|
|
+ if (urlParams.hideOptions !== undefined) {
|
|
|
|
|
+ const graphTypeContainer = document.getElementById("graphTypeContainer");
|
|
|
|
|
+ const topTabs = document.getElementById("topTabContainer");
|
|
|
|
|
+ const configDiv = document.getElementById("selectorSubtypes");
|
|
|
|
|
+ const plotContainer = document.getElementById("mainPlot");
|
|
|
|
|
+ if (topTabs && graphTypeContainer && configDiv) {
|
|
|
|
|
+ topTabs.style.display = "none";
|
|
|
|
|
+ graphTypeContainer.style.display = "none";
|
|
|
|
|
+ configDiv.style.display = "none";
|
|
|
|
|
+ plotContainer?.classList.add("fullScreen");
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+};
|
|
|
|
|
+var urlParams = Object.fromEntries(new URLSearchParams(window.location.search));
|
|
|
|
|
+var loadedData = {};
|
|
|
|
|
+var graphs = [
|
|
|
|
|
+ new TopProduction(loadedData, urlParams),
|
|
|
|
|
+ new TopCompanies(loadedData, urlParams),
|
|
|
|
|
+ new MatHistory(loadedData, urlParams),
|
|
|
|
|
+ new UniverseHistory(loadedData, urlParams),
|
|
|
|
|
+ new MarketOverview(loadedData, urlParams),
|
|
|
|
|
+ new CompanyTotals(loadedData, urlParams),
|
|
|
|
|
+ new CompanyHistory(loadedData, urlParams),
|
|
|
|
|
+ new CompanyRank(loadedData, urlParams),
|
|
|
|
|
+ new CorporationBreakdown(loadedData, urlParams)
|
|
|
|
|
+];
|
|
|
|
|
+export {
|
|
|
|
|
+ graphs
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+//# debugId=D6A67DC5C3A8C1C264756E2164756E21
|