| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178 |
- // ts/popover.ts
- function setupPopover() {
- const main = document.querySelector("main");
- const popover = document.querySelector("#popover");
- main.addEventListener("mouseover", (event) => {
- const target = event.target;
- if (target.dataset.tooltip) {
- popover.textContent = target.dataset.tooltip;
- const rect = target.getBoundingClientRect();
- popover.style.left = `${rect.left}px`;
- popover.style.top = `${rect.bottom}px`;
- popover.showPopover();
- }
- });
- main.addEventListener("mouseout", (event) => {
- const target = event.target;
- if (target.dataset.tooltip)
- popover.hidePopover();
- });
- }
- // ts/roi.ts
- var roiCache = {};
- async function getROI(cx) {
- const response = await fetch(`/roi_${cx.toLowerCase()}.json`);
- const lastModified = new Date(response.headers.get("last-modified"));
- const profits = await response.json();
- return { lastModified, profits };
- }
- var lowVolume = document.querySelector("input#low-volume");
- var cxSelect = document.querySelector("select#cx");
- var expertise = {
- AGRICULTURE: "agri",
- CHEMISTRY: "chem",
- CONSTRUCTION: "const",
- ELECTRONICS: "elec",
- FOOD_INDUSTRIES: "food ind",
- FUEL_REFINING: "fuel",
- MANUFACTURING: "mfg",
- METALLURGY: "metal",
- RESOURCE_EXTRACTION: "res ext"
- };
- var expertiseSelect = document.querySelector("select#expertise");
- for (const key of Object.keys(expertise)) {
- const option = document.createElement("option");
- option.value = key;
- option.textContent = key.replace("_", " ").toLowerCase();
- expertiseSelect.appendChild(option);
- }
- 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;
- var headersInitialized = false;
- async function render() {
- const tbody = document.querySelector("tbody");
- tbody.innerHTML = "";
- const cx = cxSelect.value;
- if (!roiCache[cx])
- roiCache[cx] = getROI(cx);
- const { lastModified, profits } = await roiCache[cx];
- if (!headersInitialized) {
- const ths = document.querySelectorAll("th");
- const keys = [
- "outputs",
- "expertise",
- "profit_per_area",
- "break_even",
- "capex",
- "cost_per_day",
- "logistics_per_area",
- "market_capacity_area"
- ];
- ths.forEach((th, i) => {
- if (keys[i]) {
- th.style.cursor = "pointer";
- th.title = "Click to sort";
- th.addEventListener("click", () => {
- if (currentSortKey === keys[i]) {
- currentSortAsc = !currentSortAsc;
- } else {
- currentSortKey = keys[i];
- currentSortAsc = keys[i] === "break_even" ? true : false;
- }
- render();
- });
- }
- });
- headersInitialized = true;
- }
- 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 buildingFound = false;
- buildingSelect.innerHTML = '<option value="">(all)</option>';
- for (const building of buildings)
- if (expertiseSelect.value === "" || expertiseSelect.value === building.expertise) {
- const option = document.createElement("option");
- option.value = building.ticker;
- option.textContent = building.ticker;
- if (building.ticker === selectedBuilding) {
- buildingFound = true;
- option.selected = true;
- }
- buildingSelect.appendChild(option);
- }
- if (!buildingFound)
- selectedBuilding = "";
- const filteredProfits = profits.filter((p) => {
- const volumeRatio = p.output_per_day / p.average_traded_7d;
- if (!lowVolume.checked && volumeRatio > 0.05)
- return false;
- if (expertiseSelect.value !== "" && p.expertise !== expertiseSelect.value)
- return false;
- if (selectedBuilding !== "" && p.building !== selectedBuilding)
- return false;
- return true;
- });
- filteredProfits.sort((a, b) => {
- let valA = a[currentSortKey];
- let valB = b[currentSortKey];
- if (currentSortKey === "outputs") {
- valA = a.outputs.map((o) => o.ticker).join(", ");
- valB = b.outputs.map((o) => o.ticker).join(", ");
- }
- if (valA < valB)
- return currentSortAsc ? -1 : 1;
- if (valA > valB)
- return currentSortAsc ? 1 : -1;
- return 0;
- });
- for (const p of filteredProfits) {
- const tr = document.createElement("tr");
- tr.innerHTML = `
- <td>${p.outputs.map((o) => o.ticker).join(", ")}</td>
- <td>${expertise[p.expertise]}</td>
- <td style="color: ${color(p.profit_per_area, 0, 300)}">${formatDecimal(p.profit_per_area)}</td>
- <td><span style="color: ${color(p.break_even, 30, 3)}">${formatDecimal(p.break_even)}</span>d</td>
- <td style="color: ${color(p.capex, 300000, 40000)}">${formatWhole(p.capex)}</td>
- <td style="color: ${color(p.cost_per_day, 40000, 1000)}">${formatWhole(p.cost_per_day)}</td>
- <td style="color: ${color(p.logistics_per_area, 2, 0.2)}">${formatDecimal(p.logistics_per_area)}</td>
- <td style="color: ${color(p.market_capacity_area, 20, 500)}">${formatWhole(p.market_capacity_area)}</td>
- `;
- const output = tr.querySelector("td");
- output.dataset.tooltip = p.recipe;
- const profitCell = tr.querySelectorAll("td")[2];
- const revenue = p.outputs.reduce((sum, o) => sum + o.amount * o.vwap_7d, 0);
- const inputCost = p.input_costs.reduce((sum, o) => sum + o.amount * o.vwap_7d, 0);
- profitCell.dataset.tooltip = formatMatPrices(p.outputs) + `
- ` + formatMatPrices(p.input_costs) + `
- ` + "worker consumables: " + formatWhole(p.worker_consumable_cost_per_day) + `
- ` + `(${formatWhole(revenue)} - ${formatWhole(inputCost)}) × ${formatDecimal(p.runs_per_day)} runs ` + `- ${formatWhole(p.worker_consumable_cost_per_day)} = ${formatDecimal(p.profit_per_day)}
- ` + `${formatDecimal(p.profit_per_day)} / ${formatWhole(p.area)} area = ${formatDecimal(p.profit_per_area)}`;
- const marketCell = tr.querySelectorAll("td")[7];
- 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 })}`;
- }
- function color(n, low, high) {
- const scale = Math.min(Math.max((n - low) / (high - low), 0), 1);
- return `color-mix(in xyz, #0aa ${scale * 100}%, #f80)`;
- }
- function formatMatPrices(matPrices) {
- return matPrices.map(({ ticker, amount, vwap_7d }) => `${ticker}: ${amount} × ${formatDecimal(vwap_7d)} = ${formatWhole(amount * vwap_7d)}`).join(`
- `);
- }
- setupPopover();
- lowVolume.addEventListener("change", render);
- cxSelect.addEventListener("change", render);
- expertiseSelect.addEventListener("change", render);
- buildingSelect.addEventListener("change", render);
- render();
- //# debugId=360E975B4A4AE32464756E2164756E21
|