main.js 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844
  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: ', [['Pie', 'Bar'], ['pie', 'bar']]);
  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. console.log(volumeArray);
  288. // Extract tickers and volumes into separate arrays
  289. const tickers = volumeArray.map(item => item.ticker);
  290. const volumes = volumeArray.map(item => item.volume);
  291. Plotly.newPlot(container, {
  292. data: [{ x: tickers, y: volumes, type: 'bar' , marker: {color: 'rgb(247, 166, 0)'}, hovertemplate: '%{x}: %{y:,.3~s}<extra></extra>'}],
  293. layout: {width: 800, height: 400,
  294. title: {text: 'Top ' + titles[metric] + ' - ' + prettyMonthName(month),
  295. font: {color: '#eee', family: '"Droid Sans", sans-serif'},
  296. },
  297. xaxis: {
  298. title: {
  299. text: 'Ticker',
  300. font: {color: '#bbb', family: '"Droid Sans", sans-serif'}
  301. },
  302. tickfont: {color: '#666', family: '"Droid Sans", sans-serif'},
  303. range: [-0.5, 29.5],
  304. tickangle: -45
  305. },
  306. yaxis: {
  307. title: {
  308. text: prettyModeNames[metric] + ' [$/day]',
  309. font: {color: '#bbb', family: '"Droid Sans", sans-serif'}
  310. },
  311. tickfont: {color: '#666', family: '"Droid Sans", sans-serif'},
  312. range: [(metric == 'deficit' ? null : 0), (metric == 'deficit' ? 0 : null)],
  313. gridcolor: '#323232'
  314. },
  315. plot_bgcolor: '#252525',
  316. paper_bgcolor: '#252525',
  317. dragmode: 'pan'
  318. },
  319. config: {
  320. displayModeBar: true,
  321. modeBarButtonsToRemove: ['lasso2d'], // Remove unwanted buttons
  322. displaylogo: false,
  323. scrollZoom: true
  324. }
  325. });
  326. }
  327. function generateTopCompanyGraph(container, companyData, knownCompanies, month, metric)
  328. {
  329. // Convert the data object into an array of [companyID, volume] pairs
  330. const volumeArray = Object.entries(companyData.totals).map(([companyID, info]) => ({
  331. companyID,
  332. volume: info[metric]
  333. }));
  334. // Sort the array by volume in descending order
  335. volumeArray.sort((a, b) => b.volume - a.volume);
  336. // Extract tickers and volumes into separate arrays
  337. const companyIDs = volumeArray.map(item => item.companyID);
  338. const volumes = volumeArray.map(item => item.volume);
  339. const companyNames = [];
  340. // Print unknown top 40 companies
  341. companyIDs.slice(0,40).forEach(id => {
  342. if(!knownCompanies[id])
  343. {
  344. console.log(id)
  345. }
  346. });
  347. companyIDs.forEach(id => {
  348. companyNames.push(knownCompanies[id] || (id.slice(0, 5) + "..."));
  349. });
  350. Plotly.newPlot(container, {
  351. data: [{ x: companyNames, y: volumes, type: 'bar' , marker: {color: 'rgb(247, 166, 0)'}, hovertemplate: '%{x}: %{y:,.3~s}<extra></extra>'}],
  352. layout: { width: 800, height: 400,
  353. title: {text: 'Top Companies (' + prettyModeNames[metric] + ') - ' + prettyMonthName(month),
  354. font: {color: '#eee', family: '"Droid Sans", sans-serif'},
  355. },
  356. xaxis: {
  357. title: {
  358. text: 'Player',
  359. font: {color: '#bbb', family: '"Droid Sans", sans-serif'}
  360. },
  361. tickfont: {color: '#666', family: '"Droid Sans", sans-serif'},
  362. range: [-0.5, 29.5],
  363. tickangle: -45
  364. },
  365. yaxis: {
  366. title: {
  367. text: prettyModeNames[metric] + ' [$/day]',
  368. font: {color: '#bbb', family: '"Droid Sans", sans-serif'}
  369. },
  370. tickfont: {color: '#666', family: '"Droid Sans", sans-serif'},
  371. range: [0, null],
  372. gridcolor: '#323232'
  373. },
  374. plot_bgcolor: '#252525',
  375. paper_bgcolor: '#252525',
  376. dragmode: 'pan',
  377. margin: {b: 120}
  378. },
  379. config: {
  380. displayModeBar: true,
  381. modeBarButtonsToRemove: ['lasso2d'], // Remove unwanted buttons
  382. displaylogo: false,
  383. scrollZoom: true
  384. }
  385. });
  386. }
  387. function generateMatGraph(container, months, data, ticker, metric)
  388. {
  389. const titles = {
  390. 'profit': 'Production Profit History of ',
  391. 'volume': 'Production Volume History of ',
  392. 'amount': 'Production Amount History of ',
  393. 'price': 'Price History of ',
  394. 'consumed': 'Consumption History of ',
  395. 'surplus': 'Surplus Production History of '
  396. }
  397. const yAxis = {
  398. 'profit': 'Daily Profit [$/day]',
  399. 'volume': 'Daily Volume [$/day]',
  400. 'amount': 'Daily Production [per day]',
  401. 'price': 'Price [$]',
  402. 'consumed': 'Daily Consumption [per day]',
  403. 'surplus': 'Daily Surplus [per day]'
  404. }
  405. Plotly.newPlot(container, {
  406. data: [{ x: months, y: data, type: 'bar' , marker: {color: 'rgb(247, 166, 0)'}, hovertemplate: '%{x}: %{y:,.3~s}<extra></extra>'}],
  407. layout: { width: 800, height: 400,
  408. title: {text: titles[metric] + ticker,
  409. font: {color: '#eee', family: '"Droid Sans", sans-serif'},
  410. },
  411. xaxis: {
  412. title: {
  413. text: 'Month',
  414. font: {color: '#bbb', family: '"Droid Sans", sans-serif'}
  415. },
  416. tickfont: {color: '#666', family: '"Droid Sans", sans-serif'},
  417. tickangle: -45
  418. },
  419. yaxis: {
  420. title: {
  421. text: yAxis[metric],
  422. font: {color: '#bbb', family: '"Droid Sans", sans-serif'}
  423. },
  424. tickfont: {color: '#666', family: '"Droid Sans", sans-serif'},
  425. gridcolor: '#323232'
  426. },
  427. plot_bgcolor: '#252525',
  428. paper_bgcolor: '#252525',
  429. dragmode: 'pan'
  430. },
  431. config: {
  432. displayModeBar: true,
  433. modeBarButtonsToRemove: ['lasso2d'], // Remove unwanted buttons
  434. displaylogo: false,
  435. scrollZoom: true
  436. }
  437. });
  438. }
  439. function generateCompanyGraph(container, chartType, data, companyName, month, metric)
  440. {
  441. if(!data){return;}
  442. const titles = {
  443. 'profit': 'Production Profit Breakdown of ',
  444. 'volume': 'Production Volume Breakdown of ',
  445. }
  446. var mats = Object.keys(data);
  447. var values = mats.map(ticker => data[ticker][metric]);
  448. var indices = values.map((_, i) => i).sort((a, b) => values[b] - values[a]);
  449. mats = indices.map(i => mats[i]);
  450. values = indices.map(i => values[i]);
  451. if(chartType == 'pie')
  452. {
  453. // Filter out negative values
  454. indices = values
  455. .map((v, i) => i)
  456. .filter(i => values[i] >= 0);
  457. mats = indices.map(i => mats[i]);
  458. values = indices.map(i => values[i]);
  459. Plotly.newPlot(container, {
  460. 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>'}],
  461. layout: { width: 800, height: 400,
  462. title: {text: titles[metric] + companyName + ' - ' + prettyMonthName(month),
  463. font: {color: '#eee', family: '"Droid Sans", sans-serif'},
  464. },
  465. plot_bgcolor: '#252525',
  466. paper_bgcolor: '#252525',
  467. },
  468. config: {
  469. displayModeBar: true,
  470. modeBarButtonsToRemove: ['lasso2d'], // Remove unwanted buttons
  471. displaylogo: false,
  472. scrollZoom: true
  473. }
  474. });
  475. }
  476. else
  477. {
  478. Plotly.newPlot(container, {
  479. data: [{ x: mats, y: values, type: 'bar' , marker: {color: 'rgb(247, 166, 0)'}, hovertemplate: '%{x}: %{y:,.3~s}<extra></extra>'}],
  480. layout: { width: 800, height: 400,
  481. title: {text: titles[metric] + companyName + ' - ' + prettyMonthName(month),
  482. font: {color: '#eee', family: '"Droid Sans", sans-serif'},
  483. },
  484. xaxis: {
  485. title: {
  486. text: 'Ticker',
  487. font: {color: '#bbb', family: '"Droid Sans", sans-serif'}
  488. },
  489. tickfont: {color: '#666', family: '"Droid Sans", sans-serif'},
  490. range: [-0.5, Math.min(mats.length, 30) - 0.5],
  491. tickangle: -45
  492. },
  493. yaxis: {
  494. title: {
  495. text: prettyModeNames[metric] + ' [$/day]',
  496. font: {color: '#bbb', family: '"Droid Sans", sans-serif'}
  497. },
  498. tickfont: {color: '#666', family: '"Droid Sans", sans-serif'},
  499. range: [0, null],
  500. gridcolor: '#323232'
  501. },
  502. plot_bgcolor: '#252525',
  503. paper_bgcolor: '#252525',
  504. dragmode: 'pan'
  505. },
  506. config: {
  507. displayModeBar: true,
  508. modeBarButtonsToRemove: ['lasso2d'], // Remove unwanted buttons
  509. displaylogo: false,
  510. scrollZoom: true
  511. }
  512. });
  513. }
  514. }
  515. function generateCompanyHistoryGraph(container, months, data, companyName, metric)
  516. {
  517. const titles = {
  518. 'profit': 'Production Profit History of ',
  519. 'volume': 'Production Volume History of ',
  520. }
  521. const yAxis = {
  522. 'profit': 'Daily Profit [$/day]',
  523. 'volume': 'Daily Volume [$/day]'
  524. }
  525. Plotly.newPlot(container, {
  526. data: [{ x: months, y: data, type: 'bar' , marker: {color: 'rgb(247, 166, 0)'}, hovertemplate: '%{x}: %{y:,.3~s}<extra></extra>'}],
  527. layout: { width: 800, height: 400,
  528. title: {text: titles[metric] + companyName,
  529. font: {color: '#eee', family: '"Droid Sans", sans-serif'},
  530. },
  531. xaxis: {
  532. title: {
  533. text: 'Month',
  534. font: {color: '#bbb', family: '"Droid Sans", sans-serif'}
  535. },
  536. tickfont: {color: '#666', family: '"Droid Sans", sans-serif'},
  537. tickangle: -45
  538. },
  539. yaxis: {
  540. title: {
  541. text: yAxis[metric],
  542. font: {color: '#bbb', family: '"Droid Sans", sans-serif'}
  543. },
  544. tickfont: {color: '#666', family: '"Droid Sans", sans-serif'},
  545. gridcolor: '#323232'
  546. },
  547. plot_bgcolor: '#252525',
  548. paper_bgcolor: '#252525',
  549. dragmode: 'pan'
  550. },
  551. config: {
  552. displayModeBar: true,
  553. modeBarButtonsToRemove: ['lasso2d'], // Remove unwanted buttons
  554. displaylogo: false,
  555. scrollZoom: true
  556. }
  557. });
  558. }
  559. function generateRankChart(containerID, currentData, prevData, companyName, currentMonth)
  560. {
  561. if(!currentData){return;}
  562. const container = document.getElementById(containerID);
  563. const tickers = Object.keys(currentData);
  564. const currentRanks = tickers.map(ticker => ({'ticker': ticker, 'currentRank': currentData[ticker].rank, 'data': currentData[ticker]}));
  565. currentRanks.forEach(mat => {
  566. if(prevData && prevData[mat.ticker])
  567. {
  568. mat.prevRank = prevData[mat.ticker].rank;
  569. }
  570. });
  571. currentRanks.sort((x, y) => x.currentRank - y.currentRank);
  572. // Create title
  573. const title = document.createElement("div");
  574. title.textContent = "Production Ranking of " + companyName + " - " + prettyMonthName(currentMonth);
  575. title.classList.add("title");
  576. container.appendChild(title);
  577. // Start creating table
  578. const table = document.createElement("table");
  579. container.appendChild(table);
  580. // Table header
  581. const header = document.createElement("thead");
  582. table.appendChild(header);
  583. const headRow = document.createElement("tr");
  584. header.appendChild(headRow);
  585. const headers = ["Rank", "Ticker", "Amount [/day]", "Volume [$/day]", "Profit [$/day]"]
  586. headers.forEach(label => {
  587. const headerColumn = document.createElement("th");
  588. headerColumn.textContent = label;
  589. headRow.appendChild(headerColumn);
  590. });
  591. const body = document.createElement("tbody");
  592. table.appendChild(body);
  593. currentRanks.forEach(mat =>
  594. {
  595. const row = document.createElement("tr");
  596. body.appendChild(row);
  597. const rankColumn = document.createElement("td");
  598. const rankWrapper = document.createElement("div");
  599. rankWrapper.style.display = "flex";
  600. rankColumn.appendChild(rankWrapper);
  601. if(prevData)
  602. {
  603. const rankSymbol = document.createElement("div");
  604. rankSymbol.style.width = "14px";
  605. rankSymbol.style.minWidth = "14px";
  606. rankSymbol.style.marginRight = "2px";
  607. if(mat.prevRank && mat.prevRank != mat.currentRank)
  608. {
  609. const increasing = mat.prevRank && mat.prevRank < mat.currentRank;
  610. rankSymbol.textContent = increasing ? "▼" : "▲";
  611. rankSymbol.style.color = increasing ? "#d9534f" : "#5cb85c";
  612. }
  613. rankWrapper.appendChild(rankSymbol);
  614. }
  615. const rankNum = document.createElement("div");
  616. rankNum.textContent = mat.currentRank;
  617. rankWrapper.appendChild(rankNum);
  618. row.appendChild(rankColumn);
  619. const tickerColumn = document.createElement("td")
  620. tickerColumn.textContent = mat.ticker;
  621. row.appendChild(tickerColumn);
  622. const amountColumn = document.createElement("td")
  623. amountColumn.textContent = mat.data.amount.toLocaleString(undefined, {maximumFractionDigits: 1});
  624. row.appendChild(amountColumn);
  625. const volumeColumn = document.createElement("td")
  626. volumeColumn.textContent = "$" + mat.data.volume.toLocaleString(undefined, {notation: 'compact', maxixmumSignificantDigits: 3});
  627. row.appendChild(volumeColumn);
  628. const profitColumn = document.createElement("td")
  629. profitColumn.textContent = "$" + mat.data.profit.toLocaleString(undefined, {notation: 'compact', maxixmumSignificantDigits: 3});
  630. row.appendChild(profitColumn);
  631. });
  632. }
  633. // Util functions
  634. function prettyMonthName(monthStr)
  635. {
  636. const monthAbv = monthStr.substring(0,3);
  637. const monthNum = monthStr.substring(3);
  638. return fullMonthNames[monthAbv] + " 30" + monthNum;
  639. }
  640. // Remove all the children of a given element
  641. function clearChildren(elem)
  642. {
  643. elem.textContent = "";
  644. while(elem.children[0])
  645. {
  646. elem.removeChild(elem.children[0]);
  647. }
  648. return;
  649. }
  650. // Add options to a selector
  651. function addOptions(selector, options, values)
  652. {
  653. for(var i = 0; i < options.length; i++)
  654. {
  655. const optionElem = document.createElement("option");
  656. optionElem.textContent = options[i];
  657. optionElem.value = values ? values[i] : options[i];
  658. selector.appendChild(optionElem);
  659. }
  660. }
  661. function wrapInDiv(elem) // Wrap selector element in a div to center it and give it margin
  662. {
  663. const div = document.createElement('div');
  664. div.appendChild(elem);
  665. return div;
  666. }
  667. function addInput(inputType, id, label, values, defaultValue)
  668. {
  669. const labelElem = document.createElement('label');
  670. labelElem.textContent = label;
  671. const inputElem = document.createElement(inputType);
  672. inputElem.id = id;
  673. inputElem.classList.add("plotSelector");
  674. if(inputType == 'select')
  675. {
  676. addOptions(inputElem, values[0], values[1]);
  677. if(defaultValue)
  678. {
  679. inputElem.value = defaultValue;
  680. }
  681. }
  682. inputElem.addEventListener("change", function() {
  683. switchPlot();
  684. });
  685. labelElem.appendChild(inputElem);
  686. return wrapInDiv(labelElem);
  687. }
  688. async function getCompanyInfo()
  689. {
  690. const usernameInput = document.getElementById('username');
  691. if(!usernameInput.value){return;}
  692. fetch('https://rest.fnar.net/user/' + usernameInput.value)
  693. .then(response => response.json())
  694. .then(data => {
  695. const companyID = data.CompanyId;
  696. const companyName = data.UserName;
  697. if(!companyID || !companyName){return;}
  698. const companyIDInput = document.getElementById('companyID');
  699. companyIDInput.value = companyID;
  700. const companyNameInput = document.getElementById('companyName');
  701. companyNameInput.value = companyName;
  702. switchPlot();
  703. })
  704. .catch(error => {alert('Bad Response: Check Username'); console.error(error)});
  705. }
  706. const fullMonthNames = {
  707. "jan": "January",
  708. "feb": "February",
  709. "mar": "March",
  710. "apr": "April",
  711. "may": "May",
  712. "jun": "June",
  713. "jul": "July",
  714. "aug": "August",
  715. "sep": "September",
  716. "oct": "October",
  717. "nov": "November",
  718. "dec": "December"
  719. }
  720. const prettyModeNames = {
  721. "amount": "Amount",
  722. "profit": "Profit",
  723. "volume": "Volume",
  724. "price": "Price"
  725. }