main.js 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911
  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: ', [['Treemap', 'Treemap (categories)', 'Pie', 'Bar'], ['treemap', 'treemap-categories', '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 == 'treemap' || chartType == 'treemap-categories')
  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. var colors = mats.map(m => materialsToColors[m] || '#000000');
  460. var parents = chartType == 'treemap-categories'
  461. ? mats.map(m => materialsToCategories[m] || 'Other')
  462. : mats.map(m => 'Total');
  463. var totalValue = 0;
  464. var categoryValues = {};
  465. for (const i of indices) {
  466. totalValue += values[i];
  467. const category = parents[i];
  468. categoryValues[category] = (categoryValues[category] || 0) + values[i];
  469. }
  470. if (chartType == 'treemap-categories') {
  471. for (const category in categoryValues) {
  472. mats.push(category);
  473. values.push(categoryValues[category]);
  474. colors.push(materialCategoryColors[category] || '#000000');
  475. parents.push('Total');
  476. }
  477. }
  478. values.push(totalValue);
  479. mats.push('Total');
  480. parents.push('');
  481. colors.push('#252525');
  482. Plotly.newPlot(container, {
  483. data: [
  484. {
  485. type: 'treemap',
  486. labels: mats,
  487. parents: parents,
  488. values: values,
  489. maxdepth: 2,
  490. branchvalues: 'total',
  491. marker: {
  492. colors: colors,
  493. },
  494. tiling: {
  495. pad: 0,
  496. },
  497. textposition: 'middle center',
  498. hovertemplate: '%{label}<br>$%{value:,.3~s}/day<br>%{percentEntry:.2%}<extra></extra>'
  499. }
  500. ],
  501. layout: { width: 800, height: 600,
  502. title: {text: titles[metric] + companyName + ' - ' + prettyMonthName(month),
  503. font: {color: '#eee', family: '"Droid Sans", sans-serif'},
  504. },
  505. plot_bgcolor: '#252525',
  506. paper_bgcolor: '#252525',
  507. },
  508. config: {
  509. displaylogo: false,
  510. }
  511. });
  512. }
  513. else if(chartType == 'pie')
  514. {
  515. // Filter out negative values
  516. indices = values
  517. .map((v, i) => i)
  518. .filter(i => values[i] >= 0);
  519. mats = indices.map(i => mats[i]);
  520. values = indices.map(i => values[i]);
  521. Plotly.newPlot(container, {
  522. 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>'}],
  523. layout: { width: 800, height: 400,
  524. title: {text: titles[metric] + companyName + ' - ' + prettyMonthName(month),
  525. font: {color: '#eee', family: '"Droid Sans", sans-serif'},
  526. },
  527. plot_bgcolor: '#252525',
  528. paper_bgcolor: '#252525',
  529. },
  530. config: {
  531. displayModeBar: true,
  532. modeBarButtonsToRemove: ['lasso2d'], // Remove unwanted buttons
  533. displaylogo: false,
  534. scrollZoom: true
  535. }
  536. });
  537. }
  538. else
  539. {
  540. Plotly.newPlot(container, {
  541. data: [{ x: mats, y: values, type: 'bar' , marker: {color: 'rgb(247, 166, 0)'}, hovertemplate: '%{x}: %{y:,.3~s}<extra></extra>'}],
  542. layout: { width: 800, height: 400,
  543. title: {text: titles[metric] + companyName + ' - ' + prettyMonthName(month),
  544. font: {color: '#eee', family: '"Droid Sans", sans-serif'},
  545. },
  546. xaxis: {
  547. title: {
  548. text: 'Ticker',
  549. font: {color: '#bbb', family: '"Droid Sans", sans-serif'}
  550. },
  551. tickfont: {color: '#666', family: '"Droid Sans", sans-serif'},
  552. range: [-0.5, Math.min(mats.length, 30) - 0.5],
  553. tickangle: -45
  554. },
  555. yaxis: {
  556. title: {
  557. text: prettyModeNames[metric] + ' [$/day]',
  558. font: {color: '#bbb', family: '"Droid Sans", sans-serif'}
  559. },
  560. tickfont: {color: '#666', family: '"Droid Sans", sans-serif'},
  561. range: [0, null],
  562. gridcolor: '#323232'
  563. },
  564. plot_bgcolor: '#252525',
  565. paper_bgcolor: '#252525',
  566. dragmode: 'pan'
  567. },
  568. config: {
  569. displayModeBar: true,
  570. modeBarButtonsToRemove: ['lasso2d'], // Remove unwanted buttons
  571. displaylogo: false,
  572. scrollZoom: true
  573. }
  574. });
  575. }
  576. }
  577. function generateCompanyHistoryGraph(container, months, data, companyName, metric)
  578. {
  579. const titles = {
  580. 'profit': 'Production Profit History of ',
  581. 'volume': 'Production Volume History of ',
  582. }
  583. const yAxis = {
  584. 'profit': 'Daily Profit [$/day]',
  585. 'volume': 'Daily Volume [$/day]'
  586. }
  587. Plotly.newPlot(container, {
  588. data: [{ x: months, y: data, type: 'bar' , marker: {color: 'rgb(247, 166, 0)'}, hovertemplate: '%{x}: %{y:,.3~s}<extra></extra>'}],
  589. layout: { width: 800, height: 400,
  590. title: {text: titles[metric] + companyName,
  591. font: {color: '#eee', family: '"Droid Sans", sans-serif'},
  592. },
  593. xaxis: {
  594. title: {
  595. text: 'Month',
  596. font: {color: '#bbb', family: '"Droid Sans", sans-serif'}
  597. },
  598. tickfont: {color: '#666', family: '"Droid Sans", sans-serif'},
  599. tickangle: -45
  600. },
  601. yaxis: {
  602. title: {
  603. text: yAxis[metric],
  604. font: {color: '#bbb', family: '"Droid Sans", sans-serif'}
  605. },
  606. tickfont: {color: '#666', family: '"Droid Sans", sans-serif'},
  607. gridcolor: '#323232'
  608. },
  609. plot_bgcolor: '#252525',
  610. paper_bgcolor: '#252525',
  611. dragmode: 'pan'
  612. },
  613. config: {
  614. displayModeBar: true,
  615. modeBarButtonsToRemove: ['lasso2d'], // Remove unwanted buttons
  616. displaylogo: false,
  617. scrollZoom: true
  618. }
  619. });
  620. }
  621. function generateRankChart(containerID, currentData, prevData, companyName, currentMonth)
  622. {
  623. if(!currentData){return;}
  624. const container = document.getElementById(containerID);
  625. const tickers = Object.keys(currentData);
  626. const currentRanks = tickers.map(ticker => ({'ticker': ticker, 'currentRank': currentData[ticker].rank, 'data': currentData[ticker]}));
  627. currentRanks.forEach(mat => {
  628. if(prevData && prevData[mat.ticker])
  629. {
  630. mat.prevRank = prevData[mat.ticker].rank;
  631. }
  632. });
  633. currentRanks.sort((x, y) => x.currentRank - y.currentRank);
  634. // Create title
  635. const title = document.createElement("div");
  636. title.textContent = "Production Ranking of " + companyName + " - " + prettyMonthName(currentMonth);
  637. title.classList.add("title");
  638. container.appendChild(title);
  639. // Start creating table
  640. const table = document.createElement("table");
  641. container.appendChild(table);
  642. // Table header
  643. const header = document.createElement("thead");
  644. table.appendChild(header);
  645. const headRow = document.createElement("tr");
  646. header.appendChild(headRow);
  647. const headers = ["Rank", "Ticker", "Amount [/day]", "Volume [$/day]", "Profit [$/day]"]
  648. headers.forEach(label => {
  649. const headerColumn = document.createElement("th");
  650. headerColumn.textContent = label;
  651. headRow.appendChild(headerColumn);
  652. });
  653. const body = document.createElement("tbody");
  654. table.appendChild(body);
  655. currentRanks.forEach(mat =>
  656. {
  657. const row = document.createElement("tr");
  658. body.appendChild(row);
  659. const rankColumn = document.createElement("td");
  660. const rankWrapper = document.createElement("div");
  661. rankWrapper.style.display = "flex";
  662. rankColumn.appendChild(rankWrapper);
  663. if(prevData)
  664. {
  665. const rankSymbol = document.createElement("div");
  666. rankSymbol.style.width = "14px";
  667. rankSymbol.style.minWidth = "14px";
  668. rankSymbol.style.marginRight = "2px";
  669. if(mat.prevRank && mat.prevRank != mat.currentRank)
  670. {
  671. const increasing = mat.prevRank && mat.prevRank < mat.currentRank;
  672. rankSymbol.textContent = increasing ? "▼" : "▲";
  673. rankSymbol.style.color = increasing ? "#d9534f" : "#5cb85c";
  674. }
  675. rankWrapper.appendChild(rankSymbol);
  676. }
  677. const rankNum = document.createElement("div");
  678. rankNum.textContent = mat.currentRank;
  679. rankWrapper.appendChild(rankNum);
  680. row.appendChild(rankColumn);
  681. const tickerColumn = document.createElement("td")
  682. tickerColumn.textContent = mat.ticker;
  683. row.appendChild(tickerColumn);
  684. const amountColumn = document.createElement("td")
  685. amountColumn.textContent = mat.data.amount.toLocaleString(undefined, {maximumFractionDigits: 1});
  686. row.appendChild(amountColumn);
  687. const volumeColumn = document.createElement("td")
  688. volumeColumn.textContent = "$" + mat.data.volume.toLocaleString(undefined, {notation: 'compact', maxixmumSignificantDigits: 3});
  689. row.appendChild(volumeColumn);
  690. const profitColumn = document.createElement("td")
  691. profitColumn.textContent = "$" + mat.data.profit.toLocaleString(undefined, {notation: 'compact', maxixmumSignificantDigits: 3});
  692. row.appendChild(profitColumn);
  693. });
  694. }
  695. // Util functions
  696. function prettyMonthName(monthStr)
  697. {
  698. const monthAbv = monthStr.substring(0,3);
  699. const monthNum = monthStr.substring(3);
  700. return fullMonthNames[monthAbv] + " 30" + monthNum;
  701. }
  702. // Remove all the children of a given element
  703. function clearChildren(elem)
  704. {
  705. elem.textContent = "";
  706. while(elem.children[0])
  707. {
  708. elem.removeChild(elem.children[0]);
  709. }
  710. return;
  711. }
  712. // Add options to a selector
  713. function addOptions(selector, options, values)
  714. {
  715. for(var i = 0; i < options.length; i++)
  716. {
  717. const optionElem = document.createElement("option");
  718. optionElem.textContent = options[i];
  719. optionElem.value = values ? values[i] : options[i];
  720. selector.appendChild(optionElem);
  721. }
  722. }
  723. function wrapInDiv(elem) // Wrap selector element in a div to center it and give it margin
  724. {
  725. const div = document.createElement('div');
  726. div.appendChild(elem);
  727. return div;
  728. }
  729. function addInput(inputType, id, label, values, defaultValue)
  730. {
  731. const labelElem = document.createElement('label');
  732. labelElem.textContent = label;
  733. const inputElem = document.createElement(inputType);
  734. inputElem.id = id;
  735. inputElem.classList.add("plotSelector");
  736. if(inputType == 'select')
  737. {
  738. addOptions(inputElem, values[0], values[1]);
  739. if(defaultValue)
  740. {
  741. inputElem.value = defaultValue;
  742. }
  743. }
  744. inputElem.addEventListener("change", function() {
  745. switchPlot();
  746. });
  747. labelElem.appendChild(inputElem);
  748. return wrapInDiv(labelElem);
  749. }
  750. async function getCompanyInfo()
  751. {
  752. const usernameInput = document.getElementById('username');
  753. if(!usernameInput.value){return;}
  754. fetch('https://rest.fnar.net/user/' + usernameInput.value)
  755. .then(response => response.json())
  756. .then(data => {
  757. const companyID = data.CompanyId;
  758. const companyName = data.UserName;
  759. if(!companyID || !companyName){return;}
  760. const companyIDInput = document.getElementById('companyID');
  761. companyIDInput.value = companyID;
  762. const companyNameInput = document.getElementById('companyName');
  763. companyNameInput.value = companyName;
  764. switchPlot();
  765. })
  766. .catch(error => {alert('Bad Response: Check Username'); console.error(error)});
  767. }
  768. const fullMonthNames = {
  769. "jan": "January",
  770. "feb": "February",
  771. "mar": "March",
  772. "apr": "April",
  773. "may": "May",
  774. "jun": "June",
  775. "jul": "July",
  776. "aug": "August",
  777. "sep": "September",
  778. "oct": "October",
  779. "nov": "November",
  780. "dec": "December"
  781. }
  782. const prettyModeNames = {
  783. "amount": "Amount",
  784. "profit": "Profit",
  785. "volume": "Volume",
  786. "price": "Price"
  787. }