|
|
@@ -50,13 +50,20 @@ for (const key of Object.keys(expertise)) {
|
|
|
var buildingSelect = document.querySelector("select#building");
|
|
|
var formatDecimal = new Intl.NumberFormat(undefined, { maximumFractionDigits: 2, maximumSignificantDigits: 6, roundingPriority: "lessPrecision" }).format;
|
|
|
var formatWhole = new Intl.NumberFormat(undefined, { maximumFractionDigits: 0 }).format;
|
|
|
-var currentSortKey = "break_even";
|
|
|
-var currentSortAsc = true;
|
|
|
+if (localStorage.getItem("roi-cx"))
|
|
|
+ cxSelect.value = localStorage.getItem("roi-cx");
|
|
|
+if (localStorage.getItem("roi-expertise"))
|
|
|
+ expertiseSelect.value = localStorage.getItem("roi-expertise");
|
|
|
+if (localStorage.getItem("roi-low-volume"))
|
|
|
+ lowVolume.checked = localStorage.getItem("roi-low-volume") === "true";
|
|
|
+var savedBuilding = localStorage.getItem("roi-building") || "";
|
|
|
+var currentSortKey = localStorage.getItem("roi-sort-key") || "break_even";
|
|
|
+var currentSortAsc = localStorage.getItem("roi-sort-asc") !== "false";
|
|
|
var headersInitialized = false;
|
|
|
var metricControlsInitialized = false;
|
|
|
-var capexMetric = "vwap";
|
|
|
-var opexMetric = "vwap";
|
|
|
-var revenueMetric = "vwap";
|
|
|
+var capexMetric = localStorage.getItem("roi-capex-metric") || "vwap";
|
|
|
+var opexMetric = localStorage.getItem("roi-opex-metric") || "vwap";
|
|
|
+var revenueMetric = localStorage.getItem("roi-revenue-metric") || "vwap";
|
|
|
async function render() {
|
|
|
const tbody = document.querySelector("tbody");
|
|
|
tbody.innerHTML = "";
|
|
|
@@ -81,6 +88,9 @@ async function render() {
|
|
|
const table = document.querySelector("table");
|
|
|
if (table)
|
|
|
table.parentNode?.insertBefore(controls, table);
|
|
|
+ document.getElementById("capex-metric").value = capexMetric;
|
|
|
+ document.getElementById("opex-metric").value = opexMetric;
|
|
|
+ document.getElementById("revenue-metric").value = revenueMetric;
|
|
|
document.getElementById("capex-metric").addEventListener("change", (e) => {
|
|
|
capexMetric = e.target.value;
|
|
|
render();
|
|
|
@@ -110,7 +120,19 @@ async function render() {
|
|
|
ths.forEach((th, i) => {
|
|
|
if (keys[i]) {
|
|
|
th.style.cursor = "pointer";
|
|
|
- th.title = "Click to sort";
|
|
|
+ th.title = "";
|
|
|
+ if (keys[i] === "market_capacity_area") {
|
|
|
+ th.textContent = "Market Cap (Areas)";
|
|
|
+ th.dataset.tooltip = `Click to sort.
|
|
|
+
|
|
|
+Market Capacity: 7-day average traded volume ÷ daily output per area. Indicates how many areas you can build before saturating the market.`;
|
|
|
+ } else if (keys[i] === "break_even") {
|
|
|
+ th.dataset.tooltip = `Click to sort.
|
|
|
+
|
|
|
+Break Even: (CapEx + 3 days of OpEx) ÷ daily profit. Includes 3 days of operating costs as working capital.`;
|
|
|
+ } else {
|
|
|
+ th.dataset.tooltip = "Click to sort.";
|
|
|
+ }
|
|
|
th.addEventListener("click", () => {
|
|
|
if (currentSortKey === keys[i]) {
|
|
|
currentSortAsc = !currentSortAsc;
|
|
|
@@ -126,7 +148,7 @@ async function render() {
|
|
|
}
|
|
|
const buildingTickers = new Set(profits.map((p) => p.building));
|
|
|
const buildings = Array.from(buildingTickers).map((building) => ({ ticker: building, expertise: profits.find((p) => p.building === building).expertise })).sort((a, b) => a.ticker.localeCompare(b.ticker));
|
|
|
- let selectedBuilding = buildingSelect.value;
|
|
|
+ let selectedBuilding = buildingSelect.value || savedBuilding;
|
|
|
let buildingFound = false;
|
|
|
buildingSelect.innerHTML = '<option value="">(all)</option>';
|
|
|
for (const building of buildings)
|
|
|
@@ -142,6 +164,7 @@ async function render() {
|
|
|
}
|
|
|
if (!buildingFound)
|
|
|
selectedBuilding = "";
|
|
|
+ savedBuilding = "";
|
|
|
const filteredProfits = profits.filter((p) => {
|
|
|
const volumeRatio = p.output_per_day / p.average_traded_7d;
|
|
|
if (!lowVolume.checked && volumeRatio > 0.05)
|
|
|
@@ -175,7 +198,6 @@ async function render() {
|
|
|
return 0;
|
|
|
});
|
|
|
for (const p of profitsWithMetrics) {
|
|
|
- const volumeRatio = p.output_per_day / p.average_traded_7d;
|
|
|
const tr = document.createElement("tr");
|
|
|
tr.innerHTML = `
|
|
|
<td>${p.outputs.map((o) => o.ticker).join(", ")}</td>
|
|
|
@@ -199,6 +221,19 @@ async function render() {
|
|
|
marketCell.dataset.tooltip = `Market Capacity: ${formatWhole(p.average_traded_7d)} traded/day ÷ ${formatDecimal(p.output_per_day / p.area)} produced/day/area = ${formatWhole(p.market_capacity_area)} equivalent areas`;
|
|
|
tbody.appendChild(tr);
|
|
|
}
|
|
|
+ document.getElementById("last-updated").textContent = `last updated: ${lastModified.toLocaleString(undefined, { dateStyle: "full", timeStyle: "long", hour12: false })}`;
|
|
|
+ saveState();
|
|
|
+}
|
|
|
+function saveState() {
|
|
|
+ localStorage.setItem("roi-cx", cxSelect.value);
|
|
|
+ localStorage.setItem("roi-expertise", expertiseSelect.value);
|
|
|
+ localStorage.setItem("roi-building", buildingSelect.value);
|
|
|
+ localStorage.setItem("roi-low-volume", lowVolume.checked.toString());
|
|
|
+ localStorage.setItem("roi-sort-key", currentSortKey);
|
|
|
+ localStorage.setItem("roi-sort-asc", currentSortAsc.toString());
|
|
|
+ localStorage.setItem("roi-capex-metric", capexMetric);
|
|
|
+ localStorage.setItem("roi-opex-metric", opexMetric);
|
|
|
+ localStorage.setItem("roi-revenue-metric", revenueMetric);
|
|
|
}
|
|
|
function color(n, low, high) {
|
|
|
const scale = Math.min(Math.max((n - low) / (high - low), 0), 1);
|
|
|
@@ -219,4 +254,4 @@ expertiseSelect.addEventListener("change", render);
|
|
|
buildingSelect.addEventListener("change", render);
|
|
|
render();
|
|
|
|
|
|
-//# debugId=96EE1F33EAAE56CC64756E2164756E21
|
|
|
+//# debugId=2FB8029285CE8DDA64756E2164756E21
|