diff --git a/src/datum.js b/src/datum.js new file mode 100644 index 0000000..1be39eb --- /dev/null +++ b/src/datum.js @@ -0,0 +1,22 @@ +export default class Datum { + constructor(time, price) { + this._time = time; + this._price = price; + } + + getTime() { + return this._time; + } + + getPrice() { + return this._price; + } + + getX() { + return this.getTime(); + } + + getY() { + return this.getPrice(); + } +} \ No newline at end of file diff --git a/src/fetchCurrent.js b/src/fetchCurrent.js new file mode 100644 index 0000000..c7fdbe3 --- /dev/null +++ b/src/fetchCurrent.js @@ -0,0 +1,4 @@ +export default async function fetchCurrent() { + const resp = await fetch("https://data.wowtoken.app/token/current.json"); + return await resp.json(); +} diff --git a/src/fetchData.js b/src/fetchData.js new file mode 100644 index 0000000..17db168 --- /dev/null +++ b/src/fetchData.js @@ -0,0 +1,13 @@ +import Datum from "./datum"; +import urlBuilder from "./urlBuilder"; + +export default async function fetchData(currentRegionSelection, currentTimeSelection, currentAggregateSelection) { + const data = []; + const resp = await fetch(urlBuilder(currentRegionSelection, currentTimeSelection, currentAggregateSelection)); + const respData = await resp.json(); + for (let i = 0, l = respData.length; i < l; i++) { + let datum = new Datum(Date.parse(respData[i]['time']), respData[i]['value']); + data.push(datum); + } + return data; +} \ No newline at end of file diff --git a/src/index.js b/src/index.js index 8f9b685..7e054fb 100644 --- a/src/index.js +++ b/src/index.js @@ -12,6 +12,12 @@ import { import 'chartjs-adapter-dayjs-3'; import "./style.css" +import fetchCurrent from "./fetchCurrent"; +import fetchData from "./fetchData"; +import {addLoader, removeLoader} from "./loader"; +import TokenChart from "./tokenChart"; +import Datum from "./datum"; + // TODO: This file should be seperated into multiple with better ownership Chart.register( @@ -29,13 +35,14 @@ let currentRegionSelection = ''; let currentTimeSelection = ''; let currentAggregateSelection = ''; let startYAtZero = false; +let chart; const currentPriceHash = { us: 0, eu: 0, kr: 0, tw: 0 }; -let chartData = { +const chartData = { us: [], eu: [], kr: [], @@ -68,7 +75,7 @@ function populateChart() { datasets: [{ borderColor: 'gold', label: currentRegionSelection.toUpperCase() + " WoW Token Price", - data: chartJsData, + data: [], cubicInterpolationMode: 'monotone', pointRadius: 0 }] @@ -122,42 +129,35 @@ function lookupTimeUnit(query){ return lookup[query.charAt(query.length - 1)] } - async function callUpdateURL() { - let resp = await fetch("https://data.wowtoken.app/token/current.json"); - let data = await resp.json(); - updateTokens(data); + await updateTokens(await fetchCurrent()); } -function updateTokens(data) { - updateRegionalToken('us', data); - updateRegionalToken('eu', data); - updateRegionalToken('kr', data); - updateRegionalToken('tw', data); +async function updateTokens(data) { + await Promise.all([ + updateRegionalToken('us', data), + updateRegionalToken('eu', data), + updateRegionalToken('kr', data), + updateRegionalToken('tw', data) + ]); } -function updateRegionalToken(region, data) { +async function updateRegionalToken(region, data) { if (currentPriceHash[region] !== data['price_data'][region]) { currentPriceHash[region] = data['price_data'][region]; if (region === currentRegionSelection) { formatToken(); - if (currentAggregateSelection === 'none') { - addDataToChart(region, data); + const datum = new Datum(data['current_time'], data['price']); + if (currentAggregateSelection === 'none' && chart.active()) { + await chart.addDataToChart(datum); + } + else if (currentAggregateSelection === 'none' && !chart.active()) { + await chart.lateUpdate(datum); } } } } -function addDataToChart(region, data) { - if (tokenChart) { - const datum = {x: data['current_time'], y: data['price_data'][region]} - tokenChart.data.datasets.forEach((dataset) => { - dataset.data.push(datum); - }) - tokenChart.update(); - } -} - async function aggregateFunctionToggle() { // TODO: We should probably make these global or something // so if the need to be updated in the future we can do so easily @@ -177,54 +177,33 @@ async function aggregateFunctionToggle() { } } -function addLoader() { - let loader = document.getElementById('loader'); - if (!loader) { - const blank_div = document.createElement('div'); - let loaderNode = blank_div.cloneNode(); - loaderNode.id = 'loader'; - loaderNode.className = 'lds-ripple'; - loaderNode.appendChild(blank_div.cloneNode()); - loaderNode.appendChild(blank_div.cloneNode()); - let chartNode = document.getElementById('token-chart'); - chartNode.before(loaderNode); - } -} - -function removeLoader () { - let loader = document.getElementById('loader'); - if (loader) { - loader.remove(); - } -} - -function updateRegionPreference(newRegion) { +async function updateRegionPreference(newRegion) { if (newRegion !== currentRegionSelection) { - tokenChart.destroy(); + await chart.destroyChart(); addLoader(); currentRegionSelection = newRegion; } formatToken(); - pullChartData().then(populateChart); + await pullChartData(); } -function updateTimePreference(newTime) { +async function updateTimePreference(newTime) { if (newTime !== currentTimeSelection) { - tokenChart.destroy(); + await chart.destroyChart(); addLoader(); currentTimeSelection = newTime; - aggregateFunctionToggle(); + await aggregateFunctionToggle(); } - pullChartData().then(populateChart); + await pullChartData(); } -function updateAggregatePreference(newAggregate) { +async function updateAggregatePreference(newAggregate) { if (newAggregate !== currentAggregateSelection) { - tokenChart.destroy(); + await chart.destroyChart(); addLoader(); currentAggregateSelection = newAggregate; } - pullChartData().then(populateChart); + await pullChartData(); } function toggleAdvancedSetting() { @@ -241,33 +220,19 @@ function toggleAdvancedSetting() { function toggleStartYAtZero(){ startYAtZero = document.getElementById('y-start').checked; - if (tokenChart){ - tokenChart.options.scales.y.beginAtZero = startYAtZero; - tokenChart.update(); - } -} - -function urlBuilder() { - let url = "https://data.wowtoken.app/token/history/"; - if (currentAggregateSelection !== 'none') { - url += `${currentAggregateSelection}/` - } - url += `${currentRegionSelection}/${currentTimeSelection}.json` - return url; + chart.toggleYStart(startYAtZero); } async function pullChartData() { - let resp = await fetch(urlBuilder()); - let chartData = await resp.json(); - let newChartJSData = []; - for (let i = 0; i < chartData.length; i++) { - let datum = { - x: chartData[i]['time'], - y: chartData[i]['value'] - }; - newChartJSData.push(datum); + chartData[currentRegionSelection] = await fetchData(currentRegionSelection, currentTimeSelection, currentAggregateSelection); + if (!chart.active()) { + await chart.createChart(currentRegionSelection, currentTimeSelection, startYAtZero, chartData[currentRegionSelection]); + } + else { + for (let i = 0; i < chartData[currentRegionSelection].length; i++) { + await chart.addDataToChart(chartData[currentRegionSelection][i]); + } } - chartJsData = newChartJSData; removeLoader(); } @@ -428,7 +393,11 @@ function registerOptionHandlers() { document.addEventListener('DOMContentLoaded', function () { registerEventHandles(); detectURLQuery(); - Promise.all([callUpdateURL(), pullChartData()]).then(populateChart) + chart = new TokenChart(); + Promise.all([ + callUpdateURL(), + ]).then(pullChartData); + setInterval(callUpdateURL, 60*1000); }, false); diff --git a/src/loader.js b/src/loader.js new file mode 100644 index 0000000..34c8d29 --- /dev/null +++ b/src/loader.js @@ -0,0 +1,22 @@ +function addLoader() { + let loader = document.getElementById('loader'); + if (!loader) { + const blank_div = document.createElement('div'); + let loaderNode = blank_div.cloneNode(); + loaderNode.id = 'loader'; + loaderNode.className = 'lds-ripple'; + loaderNode.appendChild(blank_div.cloneNode()); + loaderNode.appendChild(blank_div.cloneNode()); + let chartNode = document.getElementById('token-chart'); + chartNode.before(loaderNode); + } +} + +function removeLoader () { + let loader = document.getElementById('loader'); + if (loader) { + loader.remove(); + } +} + +export {addLoader, removeLoader}; \ No newline at end of file diff --git a/src/tokenChart.js b/src/tokenChart.js new file mode 100644 index 0000000..a5f4a29 --- /dev/null +++ b/src/tokenChart.js @@ -0,0 +1,135 @@ +import { + Chart, + Legend, + LinearScale, + LineController, + LineElement, + PointElement, + TimeSeriesScale, + Title, + Tooltip +} from 'chart.js'; +import 'chartjs-adapter-dayjs-3'; + +function lookupTimeUnit(query){ + const lookup = { + 'h': 'day', + 'd': 'week', + 'm': 'month', + 'y': 'month', + 'l': 'year' + } + return lookup[query.charAt(query.length - 1)] +} + +export default class TokenChart { + constructor() { + this._context = document.getElementById("token-chart").getContext('2d'); + this._chartActive = false; + this._lastDatum = null; + this._lateUpdate = true + } + + async createChart(region, time, yLevel, data = []) { + const chartData = []; + let lateUpdateData = this._lastDatum; + + for (let i = 0; i < data.length; i++) { + this._lastDatum = data[i]; + chartData.push({ + x: data[i].getX(), + y: data[i].getY(), + }) + } + + if (this._lateUpdate) { + if (this._lastDatum.getPrice() !== lateUpdateData.getPrice() && + this._lastDatum.getTime() < lateUpdateData.getTime()) { + chartData.push({ + x: lateUpdateData.getX(), + y: lateUpdateData.getY(), + }) + } + this._lateUpdate = false + } + + this._chart = new Chart(this._context, { + type: 'line', + data: { + datasets: [{ + borderColor: 'gold', + label: region.toUpperCase() + " WoW Token Price", + data: chartData, + cubicInterpolationMode: 'monotone', + pointRadius: 0 + }] + }, + options: { + interaction: { + intersect: false, + mode: "index" + }, + scales: { + x: { + type: 'time', + grid: { + color: '#625f62', + }, + ticks: { + color: '#a7a4ab', + font: { + size: 18, + } + }, + time: { + unit: lookupTimeUnit(time) + } + }, + y: { + beginAtZero: yLevel, + grid: { + color: '#2f2c2f', + }, + ticks: { + color: '#a7a4ab', + font: { + size: 18, + } + } + } + }, + } + }); + this._chartActive = true; + } + + async destroyChart() { + await this._chart.destroy(); + this._chartActive = false; + } + + async lateUpdate(datum){ + this._lastDatum = datum; + this._lateUpdate = true; + } + + async addDataToChart(datum) { + this._chart.data.datasets.forEach((dataset) => { + dataset.data.push({ + x: datum.getX(), + y: datum.getY(), + }) + }); + this._chart.update(); + } + + active() { + return this._chartActive; + } + + toggleYStart(startYAtZero) { + this._chart.options.scales.y.beginAtZero = startYAtZero; + this._chart.update(); + } + +} \ No newline at end of file diff --git a/src/urlBuilder.js b/src/urlBuilder.js new file mode 100644 index 0000000..1e617d0 --- /dev/null +++ b/src/urlBuilder.js @@ -0,0 +1,8 @@ +export default function urlBuilder(currentRegionSelection, currentTimeSelection, currentAggregateSelection) { + let url = "https://data.wowtoken.app/token/history/"; + if (currentAggregateSelection !== 'none') { + url += `${currentAggregateSelection}/` + } + url += `${currentRegionSelection}/${currentTimeSelection}.json` + return url; +} \ No newline at end of file