main.js 25 KB

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