main.js 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910
  1. window.onload = function() {
  2. const graphTypeSelector = document.getElementById("graphType");
  3. const selectorSubtypes = document.getElementById("selectorSubtypes");
  4. graphTypeSelector.addEventListener("change", function() {
  5. updateSelectors(graphTypeSelector, selectorSubtypes);
  6. switchPlot();
  7. });
  8. updateSelectors(graphTypeSelector, selectorSubtypes);
  9. switchPlot();
  10. };
  11. // Update selectors based on graph type
  12. function updateSelectors(graphTypeSelector, selectorSubtypes)
  13. {
  14. const monthsPretty = ["March 3025", "April 3025", "May 3025", "June 3025"];
  15. const months = ["mar25", "apr25", "may25", "jun25"];
  16. const currentMonth = "jun25";
  17. clearChildren(selectorSubtypes);
  18. if(graphTypeSelector.value == "topProduction")
  19. {
  20. selectorSubtypes.appendChild(addInput('select', 'metric', 'Metric: ', [['Volume', 'Profit', 'Deficit'], ['volume', 'profit', 'deficit']]));
  21. selectorSubtypes.appendChild(addInput('select', 'month', 'Month: ', [monthsPretty, months], currentMonth));
  22. }
  23. else if(graphTypeSelector.value == "topCompanies")
  24. {
  25. selectorSubtypes.appendChild(addInput('select', 'metric', 'Metric: ', [['Volume', 'Profit'], ['volume', 'profit']]));
  26. selectorSubtypes.appendChild(addInput('select', 'month', 'Month: ', [monthsPretty, months], currentMonth));
  27. }
  28. else if(graphTypeSelector.value == "matHistory")
  29. {
  30. selectorSubtypes.appendChild(addInput('select', 'metric', 'Metric: ', [['Volume', 'Profit', 'Price', 'Produced', 'Consumption', 'Surplus'], ['volume', 'profit', 'price', 'amount', 'consumed', 'surplus']]));
  31. selectorSubtypes.appendChild(addInput('input', 'mat', 'Ticker: '));
  32. }
  33. else if(graphTypeSelector.value == "compTotals")
  34. {
  35. const chartTypeElem = addInput('select', 'chartType', 'Chart Type: ', [['Bar', 'Pie', 'Treemap (Mat)', 'Treemap (Cat)'], ['bar', 'pie', 'treemap', 'treemap-categories']]);
  36. chartTypeElem.style.marginLeft = "-30px";
  37. selectorSubtypes.appendChild(chartTypeElem);
  38. selectorSubtypes.appendChild(addInput('select', 'metric', 'Metric: ', [['Volume', 'Profit'], ['volume', 'profit']]));
  39. selectorSubtypes.appendChild(addInput('select', 'month', 'Month: ', [monthsPretty, months], currentMonth));
  40. // Username input and query button
  41. const usernameInput = addInput('input', 'username', 'Username: ');
  42. const submitButton = document.createElement("button");
  43. submitButton.textContent = "Query";
  44. submitButton.classList.add("queryButton");
  45. usernameInput.appendChild(submitButton);
  46. submitButton.addEventListener('click', getCompanyInfo);
  47. usernameInput.style.marginLeft = "33px";
  48. selectorSubtypes.appendChild(usernameInput);
  49. // Hidden company ID input, autofilled by query
  50. const companyIDInput = addInput('input', 'companyID', 'Company ID: ');
  51. companyIDInput.style.visibility = 'hidden';
  52. selectorSubtypes.appendChild(companyIDInput);
  53. const companyNameInput = addInput('input', 'companyName', 'Company Name: ');
  54. companyNameInput.style.visibility = 'hidden';
  55. selectorSubtypes.appendChild(companyNameInput);
  56. }
  57. else if(graphTypeSelector.value == "compHistory")
  58. {
  59. selectorSubtypes.appendChild(addInput('select', 'metric', 'Metric: ', [['Volume', 'Profit'], ['volume', 'profit']]));
  60. // Username input and query button
  61. const usernameInput = addInput('input', 'username', 'Username: ');
  62. const submitButton = document.createElement("button");
  63. submitButton.textContent = "Query";
  64. submitButton.classList.add("queryButton");
  65. usernameInput.appendChild(submitButton);
  66. submitButton.addEventListener('click', getCompanyInfo);
  67. usernameInput.style.marginLeft = "33px";
  68. selectorSubtypes.appendChild(usernameInput);
  69. // Hidden company ID input, autofilled by query
  70. const companyIDInput = addInput('input', 'companyID', 'Company ID: ');
  71. companyIDInput.style.visibility = 'hidden';
  72. selectorSubtypes.appendChild(companyIDInput);
  73. const companyNameInput = addInput('input', 'companyName', 'Company Name: ');
  74. companyNameInput.style.visibility = 'hidden';
  75. selectorSubtypes.appendChild(companyNameInput);
  76. }
  77. else if(graphTypeSelector.value == "compRank")
  78. {
  79. selectorSubtypes.appendChild(addInput('select', 'month', 'Month: ', [monthsPretty, months], currentMonth));
  80. // Username input and query button
  81. const usernameInput = addInput('input', 'username', 'Username: ');
  82. const submitButton = document.createElement("button");
  83. submitButton.textContent = "Query";
  84. submitButton.classList.add("queryButton");
  85. usernameInput.appendChild(submitButton);
  86. submitButton.addEventListener('click', getCompanyInfo);
  87. usernameInput.style.marginLeft = "33px";
  88. selectorSubtypes.appendChild(usernameInput);
  89. // Hidden company ID input, autofilled by query
  90. const companyIDInput = addInput('input', 'companyID', 'Company ID: ');
  91. companyIDInput.style.visibility = 'hidden';
  92. selectorSubtypes.appendChild(companyIDInput);
  93. const companyNameInput = addInput('input', 'companyName', 'Company Name: ');
  94. companyNameInput.style.visibility = 'hidden';
  95. selectorSubtypes.appendChild(companyNameInput);
  96. }
  97. }
  98. function switchPlot()
  99. {
  100. const typeElem = document.getElementById("graphType");
  101. var subtypeElem;
  102. var monthElem;
  103. var metricElem;
  104. var matElem;
  105. var nameElem;
  106. var idElem;
  107. const months = ['mar25', 'apr25', 'may25', 'jun25']; // Automate this later
  108. const oldGraph = document.getElementById("mainPlot");
  109. oldGraph.remove();
  110. const newGraph = document.createElement("div");
  111. newGraph.id = "mainPlot";
  112. const graphContainer = document.getElementById("mainPlotContainer");
  113. graphContainer.appendChild(newGraph);
  114. switch(typeElem.value)
  115. {
  116. case "topProduction":
  117. metricElem = document.getElementById("metric");
  118. monthElem = document.getElementById("month");
  119. promiseGenerateTopProdGraph("mainPlot", monthElem.value, metricElem.value);
  120. break;
  121. case "topCompanies":
  122. metricElem = document.getElementById("metric");
  123. monthElem = document.getElementById("month");
  124. promiseGenerateTopCompanyGraph("mainPlot", monthElem.value, metricElem.value);
  125. break;
  126. case "matHistory":
  127. metricElem = document.getElementById("metric");
  128. matElem = document.getElementById("mat");
  129. promiseGenerateMatGraph("mainPlot", matElem.value, metricElem.value, months);
  130. break;
  131. case "compTotals":
  132. subtypeElem = document.getElementById("chartType");
  133. metricElem = document.getElementById("metric");
  134. monthElem = document.getElementById("month");
  135. nameElem = document.getElementById("companyName");
  136. idElem = document.getElementById("companyID");
  137. promiseGenerateCompanyGraph("mainPlot", subtypeElem.value, nameElem.value, idElem.value, monthElem.value, metricElem.value);
  138. break;
  139. case "compHistory":
  140. metricElem = document.getElementById("metric");
  141. nameElem = document.getElementById("companyName");
  142. idElem = document.getElementById("companyID");
  143. promiseGenerateCompanyHistoryGraph("mainPlot", nameElem.value, idElem.value, metricElem.value, months);
  144. break;
  145. case "compRank":
  146. monthElem = document.getElementById("month");
  147. nameElem = document.getElementById("companyName");
  148. idElem = document.getElementById("companyID");
  149. promiseGenerateRankChart("mainPlot", nameElem.value, idElem.value, monthElem.value, months);
  150. }
  151. }
  152. function promiseGenerateRankChart(container, companyName, companyID, currentMonth, months)
  153. {
  154. const monthIndex = months.indexOf(currentMonth);
  155. const prevMonth = months[monthIndex == 0 ? 0 : monthIndex - 1]; // Get previous month to determine change
  156. fetch('data/company-data-' + currentMonth + '.json?cb=' + Date.now())
  157. .then(response => response.json()) // Parse JSON data
  158. .then(currentData => {
  159. fetch('data/company-data-' + prevMonth + '.json?cb=' + Date.now())
  160. .then(response => response.json())
  161. .then(prevData => {
  162. generateRankChart(container, currentData.individual[companyID], (monthIndex == 0 ? undefined : prevData.individual[companyID]), companyName, currentMonth); // Use the JSON data
  163. });
  164. });
  165. }
  166. function promiseGenerateCompanyHistoryGraph(container, companyName, companyID, metric, months) // Metric is either 'profit' or 'volume'
  167. {
  168. if(!companyID){return;}
  169. const validMonths = [];
  170. // Get data
  171. const fetches = months.map(month =>
  172. fetch('data/company-data-' + month + '.json?cb=' + Date.now()).then(res => res.json()).then(json => ({ "month": month, "monthData": json }))
  173. );
  174. Promise.all(fetches).then(rawData => {
  175. const data = []
  176. var hasData = false;
  177. rawData.forEach(({month, monthData}) => {
  178. const dataPoint = monthData.totals[companyID]
  179. if(dataPoint)
  180. {
  181. data.push(dataPoint[metric])
  182. validMonths.push(month);
  183. hasData = true;
  184. }
  185. });
  186. if(hasData)
  187. {
  188. generateCompanyHistoryGraph(container, months.map(month => prettyMonthName(month)), data, companyName, metric)
  189. }
  190. });
  191. }
  192. function promiseGenerateCompanyGraph(container, chartType, companyName, companyID, month, metric) // Metric is either 'profit' or 'volume'. chartType is either 'bar' or 'pie'
  193. {
  194. fetch('data/company-data-' + month + '.json?cb=' + Date.now())
  195. .then(response => response.json()) // Parse JSON data
  196. .then(data => {
  197. generateCompanyGraph(container, chartType, data.individual[companyID], companyName, month, metric); // Use the JSON data
  198. });
  199. }
  200. function promiseGenerateTopCompanyGraph(container, month, metric) // Metric is either 'profit' or 'volume'
  201. {
  202. fetch('data/company-data-' + month + '.json?cb=' + Date.now())
  203. .then(response => response.json()) // Parse JSON data
  204. .then(data => {
  205. fetch('data/knownCompanies2.json?cb=' + Date.now())
  206. .then(response => response.json())
  207. .then(knownCompanies => {
  208. generateTopCompanyGraph(container, data, knownCompanies, month, metric); // Use the JSON data
  209. });
  210. });
  211. }
  212. function promiseGenerateTopProdGraph(container, month, metric) // Metric is either 'profit' or 'volume'
  213. {
  214. fetch('data/prod-data-' + month + '.json?cb=' + Date.now())
  215. .then(response => response.json()) // Parse JSON data
  216. .then(data => {
  217. if(metric == 'deficit') // Populate deficit into data
  218. {
  219. Object.keys(data).forEach(ticker => {
  220. if(!data[ticker]['amount'] || data[ticker]['amount'] == 0){data[ticker]['deficit'] = 0; return;}
  221. data[ticker]['deficit'] = (data[ticker]['amount'] - (data[ticker]['consumed'] || 0)) * data[ticker]['volume'] / data[ticker]['amount'];
  222. });
  223. }
  224. generateTopProdGraph(container, data, month, metric); // Use the JSON data
  225. });
  226. }
  227. function promiseGenerateMatGraph(container, ticker, metric, months) // Metric is either 'profit', 'volume', or 'amount'
  228. {
  229. // Validation/sanitizing
  230. if(!ticker){return;}
  231. ticker = ticker.toUpperCase();
  232. const validMonths = [];
  233. // Get data
  234. const fetches = months.map(month =>
  235. fetch('data/prod-data-' + month + '.json?cb=' + Date.now()).then(res => res.json()).then(json => ({ "month": month, "monthData": json }))
  236. );
  237. Promise.all(fetches).then(rawData => {
  238. const data = []
  239. var hasData = false;
  240. rawData.forEach(({month, monthData}) => {
  241. const dataPoint = monthData[ticker]
  242. if(dataPoint)
  243. {
  244. if(metric == 'price')
  245. {
  246. data.push(dataPoint['amount'] == 0 ? 0 : dataPoint['volume'] / dataPoint['amount']);
  247. }
  248. else if(metric == 'surplus')
  249. {
  250. data.push(dataPoint['amount'] - dataPoint['consumed'])
  251. }
  252. else
  253. {
  254. data.push(dataPoint[metric])
  255. }
  256. validMonths.push(month)
  257. hasData = true;
  258. }
  259. });
  260. if(hasData)
  261. {
  262. generateMatGraph(container, validMonths.map(month => prettyMonthName(month)), data, ticker, metric)
  263. }
  264. });
  265. }
  266. function generateTopProdGraph(container, prodData, month, metric)
  267. {
  268. const titles = {
  269. 'profit': 'Profit Materials',
  270. 'volume': 'Production Volumes',
  271. 'deficit': 'Deficits'
  272. }
  273. // Convert the data object into an array of [ticker, volume] pairs
  274. const volumeArray = Object.entries(prodData).map(([ticker, info]) => ({
  275. ticker,
  276. volume: info[metric]
  277. }));
  278. // Sort the array by volume in descending order
  279. if(metric == 'deficit')
  280. {
  281. volumeArray.sort((a, b) => a.volume - b.volume);
  282. }
  283. else
  284. {
  285. volumeArray.sort((a, b) => b.volume - a.volume);
  286. }
  287. // Extract tickers and volumes into separate arrays
  288. const tickers = volumeArray.map(item => item.ticker);
  289. const volumes = volumeArray.map(item => item.volume);
  290. Plotly.newPlot(container, {
  291. data: [{ x: tickers, y: volumes, type: 'bar' , marker: {color: 'rgb(247, 166, 0)'}, hovertemplate: '%{x}: %{y:,.3~s}<extra></extra>'}],
  292. layout: {width: 800, height: 400,
  293. title: {text: 'Top ' + titles[metric] + ' - ' + prettyMonthName(month),
  294. font: {color: '#eee', family: '"Droid Sans", sans-serif'},
  295. },
  296. xaxis: {
  297. title: {
  298. text: 'Ticker',
  299. font: {color: '#bbb', family: '"Droid Sans", sans-serif'}
  300. },
  301. tickfont: {color: '#666', family: '"Droid Sans", sans-serif'},
  302. range: [-0.5, 29.5],
  303. tickangle: -45
  304. },
  305. yaxis: {
  306. title: {
  307. text: prettyModeNames[metric] + ' [$/day]',
  308. font: {color: '#bbb', family: '"Droid Sans", sans-serif'}
  309. },
  310. tickfont: {color: '#666', family: '"Droid Sans", sans-serif'},
  311. range: [(metric == 'deficit' ? null : 0), (metric == 'deficit' ? 0 : null)],
  312. gridcolor: '#323232'
  313. },
  314. plot_bgcolor: '#252525',
  315. paper_bgcolor: '#252525',
  316. dragmode: 'pan'
  317. },
  318. config: {
  319. displayModeBar: true,
  320. modeBarButtonsToRemove: ['lasso2d'], // Remove unwanted buttons
  321. displaylogo: false,
  322. scrollZoom: true
  323. }
  324. });
  325. }
  326. function generateTopCompanyGraph(container, companyData, knownCompanies, month, metric)
  327. {
  328. // Convert the data object into an array of [companyID, volume] pairs
  329. const volumeArray = Object.entries(companyData.totals).map(([companyID, info]) => ({
  330. companyID,
  331. volume: info[metric]
  332. }));
  333. // Sort the array by volume in descending order
  334. volumeArray.sort((a, b) => b.volume - a.volume);
  335. // Extract tickers and volumes into separate arrays
  336. const companyIDs = volumeArray.map(item => item.companyID);
  337. const volumes = volumeArray.map(item => item.volume);
  338. const companyNames = [];
  339. // Print unknown top 40 companies
  340. companyIDs.slice(0,40).forEach(id => {
  341. if(!knownCompanies[id])
  342. {
  343. console.log(id)
  344. }
  345. });
  346. companyIDs.forEach(id => {
  347. companyNames.push(knownCompanies[id] || (id.slice(0, 5) + "..."));
  348. });
  349. Plotly.newPlot(container, {
  350. data: [{ x: companyNames, y: volumes, type: 'bar' , marker: {color: 'rgb(247, 166, 0)'}, hovertemplate: '%{x}: %{y:,.3~s}<extra></extra>'}],
  351. layout: { width: 800, height: 400,
  352. title: {text: 'Top Companies (' + prettyModeNames[metric] + ') - ' + prettyMonthName(month),
  353. font: {color: '#eee', family: '"Droid Sans", sans-serif'},
  354. },
  355. xaxis: {
  356. title: {
  357. text: 'Player',
  358. font: {color: '#bbb', family: '"Droid Sans", sans-serif'}
  359. },
  360. tickfont: {color: '#666', family: '"Droid Sans", sans-serif'},
  361. range: [-0.5, 29.5],
  362. tickangle: -45
  363. },
  364. yaxis: {
  365. title: {
  366. text: prettyModeNames[metric] + ' [$/day]',
  367. font: {color: '#bbb', family: '"Droid Sans", sans-serif'}
  368. },
  369. tickfont: {color: '#666', family: '"Droid Sans", sans-serif'},
  370. range: [0, null],
  371. gridcolor: '#323232'
  372. },
  373. plot_bgcolor: '#252525',
  374. paper_bgcolor: '#252525',
  375. dragmode: 'pan',
  376. margin: {b: 120}
  377. },
  378. config: {
  379. displayModeBar: true,
  380. modeBarButtonsToRemove: ['lasso2d'], // Remove unwanted buttons
  381. displaylogo: false,
  382. scrollZoom: true
  383. }
  384. });
  385. }
  386. function generateMatGraph(container, months, data, ticker, metric)
  387. {
  388. const titles = {
  389. 'profit': 'Production Profit History of ',
  390. 'volume': 'Production Volume History of ',
  391. 'amount': 'Production Amount History of ',
  392. 'price': 'Price History of ',
  393. 'consumed': 'Consumption History of ',
  394. 'surplus': 'Surplus Production History of '
  395. }
  396. const yAxis = {
  397. 'profit': 'Daily Profit [$/day]',
  398. 'volume': 'Daily Volume [$/day]',
  399. 'amount': 'Daily Production [per day]',
  400. 'price': 'Price [$]',
  401. 'consumed': 'Daily Consumption [per day]',
  402. 'surplus': 'Daily Surplus [per day]'
  403. }
  404. Plotly.newPlot(container, {
  405. data: [{ x: months, y: data, type: 'bar' , marker: {color: 'rgb(247, 166, 0)'}, hovertemplate: '%{x}: %{y:,.3~s}<extra></extra>'}],
  406. layout: { width: 800, height: 400,
  407. title: {text: titles[metric] + ticker,
  408. font: {color: '#eee', family: '"Droid Sans", sans-serif'},
  409. },
  410. xaxis: {
  411. title: {
  412. text: 'Month',
  413. font: {color: '#bbb', family: '"Droid Sans", sans-serif'}
  414. },
  415. tickfont: {color: '#666', family: '"Droid Sans", sans-serif'},
  416. tickangle: -45
  417. },
  418. yaxis: {
  419. title: {
  420. text: yAxis[metric],
  421. font: {color: '#bbb', family: '"Droid Sans", sans-serif'}
  422. },
  423. tickfont: {color: '#666', family: '"Droid Sans", sans-serif'},
  424. gridcolor: '#323232'
  425. },
  426. plot_bgcolor: '#252525',
  427. paper_bgcolor: '#252525',
  428. dragmode: 'pan'
  429. },
  430. config: {
  431. displayModeBar: true,
  432. modeBarButtonsToRemove: ['lasso2d'], // Remove unwanted buttons
  433. displaylogo: false,
  434. scrollZoom: true
  435. }
  436. });
  437. }
  438. function generateCompanyGraph(container, chartType, data, companyName, month, metric)
  439. {
  440. if(!data){return;}
  441. const titles = {
  442. 'profit': 'Production Profit Breakdown of ',
  443. 'volume': 'Production Volume Breakdown of ',
  444. }
  445. var mats = Object.keys(data);
  446. var values = mats.map(ticker => data[ticker][metric]);
  447. var indices = values.map((_, i) => i).sort((a, b) => values[b] - values[a]);
  448. mats = indices.map(i => mats[i]);
  449. values = indices.map(i => values[i]);
  450. if(chartType == 'treemap' || chartType == 'treemap-categories')
  451. {
  452. // Filter out negative values
  453. indices = values
  454. .map((v, i) => i)
  455. .filter(i => values[i] >= 0);
  456. mats = indices.map(i => mats[i]);
  457. values = indices.map(i => values[i]);
  458. var colors = mats.map(m => materialsToColors[m] || '#000000');
  459. var parents = chartType == 'treemap-categories'
  460. ? mats.map(m => materialsToCategories[m] || 'Other')
  461. : mats.map(m => 'Total');
  462. var totalValue = 0;
  463. var categoryValues = {};
  464. for (const i of indices) {
  465. totalValue += values[i];
  466. const category = parents[i];
  467. categoryValues[category] = (categoryValues[category] || 0) + values[i];
  468. }
  469. if (chartType == 'treemap-categories') {
  470. for (const category in categoryValues) {
  471. mats.push(category);
  472. values.push(categoryValues[category]);
  473. colors.push(materialCategoryColors[category] || '#000000');
  474. parents.push('Total');
  475. }
  476. }
  477. values.push(totalValue);
  478. mats.push('Total');
  479. parents.push('');
  480. colors.push('#252525');
  481. Plotly.newPlot(container, {
  482. data: [
  483. {
  484. type: 'treemap',
  485. labels: mats,
  486. parents: parents,
  487. values: values,
  488. maxdepth: 2,
  489. branchvalues: 'total',
  490. marker: {
  491. colors: colors,
  492. },
  493. tiling: {
  494. pad: 0,
  495. },
  496. textposition: 'middle center',
  497. hovertemplate: '%{label}<br>$%{value:,.3~s}/day<br>%{percentEntry:.2%}<extra></extra>'
  498. }
  499. ],
  500. layout: { width: 800, height: 600,
  501. title: {text: titles[metric] + companyName + ' - ' + prettyMonthName(month),
  502. font: {color: '#eee', family: '"Droid Sans", sans-serif'},
  503. },
  504. plot_bgcolor: '#252525',
  505. paper_bgcolor: '#252525',
  506. },
  507. config: {
  508. displaylogo: false,
  509. }
  510. });
  511. }
  512. else if(chartType == 'pie')
  513. {
  514. // Filter out negative values
  515. indices = values
  516. .map((v, i) => i)
  517. .filter(i => values[i] >= 0);
  518. mats = indices.map(i => mats[i]);
  519. values = indices.map(i => values[i]);
  520. Plotly.newPlot(container, {
  521. data: [{ labels: mats, values: values, type: 'pie', textinfo: 'label',textposition: 'inside', insidetextorientation: 'none', automargin: false, hovertemplate: '%{label}<br>$%{value:,.3~s}/day<br>%{percent}<extra></extra>'}],
  522. layout: { width: 800, height: 400,
  523. title: {text: titles[metric] + companyName + ' - ' + prettyMonthName(month),
  524. font: {color: '#eee', family: '"Droid Sans", sans-serif'},
  525. },
  526. plot_bgcolor: '#252525',
  527. paper_bgcolor: '#252525',
  528. },
  529. config: {
  530. displayModeBar: true,
  531. modeBarButtonsToRemove: ['lasso2d'], // Remove unwanted buttons
  532. displaylogo: false,
  533. scrollZoom: true
  534. }
  535. });
  536. }
  537. else
  538. {
  539. Plotly.newPlot(container, {
  540. data: [{ x: mats, y: values, type: 'bar' , marker: {color: 'rgb(247, 166, 0)'}, hovertemplate: '%{x}: %{y:,.3~s}<extra></extra>'}],
  541. layout: { width: 800, height: 400,
  542. title: {text: titles[metric] + companyName + ' - ' + prettyMonthName(month),
  543. font: {color: '#eee', family: '"Droid Sans", sans-serif'},
  544. },
  545. xaxis: {
  546. title: {
  547. text: 'Ticker',
  548. font: {color: '#bbb', family: '"Droid Sans", sans-serif'}
  549. },
  550. tickfont: {color: '#666', family: '"Droid Sans", sans-serif'},
  551. range: [-0.5, Math.min(mats.length, 30) - 0.5],
  552. tickangle: -45
  553. },
  554. yaxis: {
  555. title: {
  556. text: prettyModeNames[metric] + ' [$/day]',
  557. font: {color: '#bbb', family: '"Droid Sans", sans-serif'}
  558. },
  559. tickfont: {color: '#666', family: '"Droid Sans", sans-serif'},
  560. range: [0, null],
  561. gridcolor: '#323232'
  562. },
  563. plot_bgcolor: '#252525',
  564. paper_bgcolor: '#252525',
  565. dragmode: 'pan'
  566. },
  567. config: {
  568. displayModeBar: true,
  569. modeBarButtonsToRemove: ['lasso2d'], // Remove unwanted buttons
  570. displaylogo: false,
  571. scrollZoom: true
  572. }
  573. });
  574. }
  575. }
  576. function generateCompanyHistoryGraph(container, months, data, companyName, metric)
  577. {
  578. const titles = {
  579. 'profit': 'Production Profit History of ',
  580. 'volume': 'Production Volume History of ',
  581. }
  582. const yAxis = {
  583. 'profit': 'Daily Profit [$/day]',
  584. 'volume': 'Daily Volume [$/day]'
  585. }
  586. Plotly.newPlot(container, {
  587. data: [{ x: months, y: data, type: 'bar' , marker: {color: 'rgb(247, 166, 0)'}, hovertemplate: '%{x}: %{y:,.3~s}<extra></extra>'}],
  588. layout: { width: 800, height: 400,
  589. title: {text: titles[metric] + companyName,
  590. font: {color: '#eee', family: '"Droid Sans", sans-serif'},
  591. },
  592. xaxis: {
  593. title: {
  594. text: 'Month',
  595. font: {color: '#bbb', family: '"Droid Sans", sans-serif'}
  596. },
  597. tickfont: {color: '#666', family: '"Droid Sans", sans-serif'},
  598. tickangle: -45
  599. },
  600. yaxis: {
  601. title: {
  602. text: yAxis[metric],
  603. font: {color: '#bbb', family: '"Droid Sans", sans-serif'}
  604. },
  605. tickfont: {color: '#666', family: '"Droid Sans", sans-serif'},
  606. gridcolor: '#323232'
  607. },
  608. plot_bgcolor: '#252525',
  609. paper_bgcolor: '#252525',
  610. dragmode: 'pan'
  611. },
  612. config: {
  613. displayModeBar: true,
  614. modeBarButtonsToRemove: ['lasso2d'], // Remove unwanted buttons
  615. displaylogo: false,
  616. scrollZoom: true
  617. }
  618. });
  619. }
  620. function generateRankChart(containerID, currentData, prevData, companyName, currentMonth)
  621. {
  622. if(!currentData){return;}
  623. const container = document.getElementById(containerID);
  624. const tickers = Object.keys(currentData);
  625. const currentRanks = tickers.map(ticker => ({'ticker': ticker, 'currentRank': currentData[ticker].rank, 'data': currentData[ticker]}));
  626. currentRanks.forEach(mat => {
  627. if(prevData && prevData[mat.ticker])
  628. {
  629. mat.prevRank = prevData[mat.ticker].rank;
  630. }
  631. });
  632. currentRanks.sort((x, y) => x.currentRank - y.currentRank);
  633. // Create title
  634. const title = document.createElement("div");
  635. title.textContent = "Production Ranking of " + companyName + " - " + prettyMonthName(currentMonth);
  636. title.classList.add("title");
  637. container.appendChild(title);
  638. // Start creating table
  639. const table = document.createElement("table");
  640. container.appendChild(table);
  641. // Table header
  642. const header = document.createElement("thead");
  643. table.appendChild(header);
  644. const headRow = document.createElement("tr");
  645. header.appendChild(headRow);
  646. const headers = ["Rank", "Ticker", "Amount [/day]", "Volume [$/day]", "Profit [$/day]"]
  647. headers.forEach(label => {
  648. const headerColumn = document.createElement("th");
  649. headerColumn.textContent = label;
  650. headRow.appendChild(headerColumn);
  651. });
  652. const body = document.createElement("tbody");
  653. table.appendChild(body);
  654. currentRanks.forEach(mat =>
  655. {
  656. const row = document.createElement("tr");
  657. body.appendChild(row);
  658. const rankColumn = document.createElement("td");
  659. const rankWrapper = document.createElement("div");
  660. rankWrapper.style.display = "flex";
  661. rankColumn.appendChild(rankWrapper);
  662. if(prevData)
  663. {
  664. const rankSymbol = document.createElement("div");
  665. rankSymbol.style.width = "14px";
  666. rankSymbol.style.minWidth = "14px";
  667. rankSymbol.style.marginRight = "2px";
  668. if(mat.prevRank && mat.prevRank != mat.currentRank)
  669. {
  670. const increasing = mat.prevRank && mat.prevRank < mat.currentRank;
  671. rankSymbol.textContent = increasing ? "▼" : "▲";
  672. rankSymbol.style.color = increasing ? "#d9534f" : "#5cb85c";
  673. }
  674. rankWrapper.appendChild(rankSymbol);
  675. }
  676. const rankNum = document.createElement("div");
  677. rankNum.textContent = mat.currentRank;
  678. rankWrapper.appendChild(rankNum);
  679. row.appendChild(rankColumn);
  680. const tickerColumn = document.createElement("td")
  681. tickerColumn.textContent = mat.ticker;
  682. row.appendChild(tickerColumn);
  683. const amountColumn = document.createElement("td")
  684. amountColumn.textContent = mat.data.amount.toLocaleString(undefined, {maximumFractionDigits: 1});
  685. row.appendChild(amountColumn);
  686. const volumeColumn = document.createElement("td")
  687. volumeColumn.textContent = "$" + mat.data.volume.toLocaleString(undefined, {notation: 'compact', maxixmumSignificantDigits: 3});
  688. row.appendChild(volumeColumn);
  689. const profitColumn = document.createElement("td")
  690. profitColumn.textContent = "$" + mat.data.profit.toLocaleString(undefined, {notation: 'compact', maxixmumSignificantDigits: 3});
  691. row.appendChild(profitColumn);
  692. });
  693. }
  694. // Util functions
  695. function prettyMonthName(monthStr)
  696. {
  697. const monthAbv = monthStr.substring(0,3);
  698. const monthNum = monthStr.substring(3);
  699. return fullMonthNames[monthAbv] + " 30" + monthNum;
  700. }
  701. // Remove all the children of a given element
  702. function clearChildren(elem)
  703. {
  704. elem.textContent = "";
  705. while(elem.children[0])
  706. {
  707. elem.removeChild(elem.children[0]);
  708. }
  709. return;
  710. }
  711. // Add options to a selector
  712. function addOptions(selector, options, values)
  713. {
  714. for(var i = 0; i < options.length; i++)
  715. {
  716. const optionElem = document.createElement("option");
  717. optionElem.textContent = options[i];
  718. optionElem.value = values ? values[i] : options[i];
  719. selector.appendChild(optionElem);
  720. }
  721. }
  722. function wrapInDiv(elem) // Wrap selector element in a div to center it and give it margin
  723. {
  724. const div = document.createElement('div');
  725. div.appendChild(elem);
  726. return div;
  727. }
  728. function addInput(inputType, id, label, values, defaultValue)
  729. {
  730. const labelElem = document.createElement('label');
  731. labelElem.textContent = label;
  732. const inputElem = document.createElement(inputType);
  733. inputElem.id = id;
  734. inputElem.classList.add("plotSelector");
  735. if(inputType == 'select')
  736. {
  737. addOptions(inputElem, values[0], values[1]);
  738. if(defaultValue)
  739. {
  740. inputElem.value = defaultValue;
  741. }
  742. }
  743. inputElem.addEventListener("change", function() {
  744. switchPlot();
  745. });
  746. labelElem.appendChild(inputElem);
  747. return wrapInDiv(labelElem);
  748. }
  749. async function getCompanyInfo()
  750. {
  751. const usernameInput = document.getElementById('username');
  752. if(!usernameInput.value){return;}
  753. fetch('https://rest.fnar.net/user/' + usernameInput.value)
  754. .then(response => response.json())
  755. .then(data => {
  756. const companyID = data.CompanyId;
  757. const companyName = data.UserName;
  758. if(!companyID || !companyName){return;}
  759. const companyIDInput = document.getElementById('companyID');
  760. companyIDInput.value = companyID;
  761. const companyNameInput = document.getElementById('companyName');
  762. companyNameInput.value = companyName;
  763. switchPlot();
  764. })
  765. .catch(error => {alert('Bad Response: Check Username'); console.error(error)});
  766. }
  767. const fullMonthNames = {
  768. "jan": "January",
  769. "feb": "February",
  770. "mar": "March",
  771. "apr": "April",
  772. "may": "May",
  773. "jun": "June",
  774. "jul": "July",
  775. "aug": "August",
  776. "sep": "September",
  777. "oct": "October",
  778. "nov": "November",
  779. "dec": "December"
  780. }
  781. const prettyModeNames = {
  782. "amount": "Amount",
  783. "profit": "Profit",
  784. "volume": "Volume",
  785. "price": "Price"
  786. }