Compare commits

...

4 Commits

7 changed files with 220 additions and 31 deletions

View File

@ -1,4 +1,4 @@
export default async function fetchCurrent() { export default async function fetchCurrent() {
const resp = await fetch("https://data.wowtoken.app/token/current.json"); const resp = await fetch("https://data.wowtoken.app/v2/current/retail.json");
return await resp.json(); return await resp.json();
} }

View File

@ -6,7 +6,7 @@ export default async function fetchData(currentRegionSelection, currentTimeSelec
const resp = await fetch(urlBuilder(currentRegionSelection, currentTimeSelection, currentAggregateSelection)); const resp = await fetch(urlBuilder(currentRegionSelection, currentTimeSelection, currentAggregateSelection));
const respData = await resp.json(); const respData = await resp.json();
for (let i = 0, l = respData.length; i < l; i++) { for (let i = 0, l = respData.length; i < l; i++) {
let datum = new Datum(Date.parse(respData[i]['time']), respData[i]['value']); const datum = new Datum(new Date(respData[i][0]), respData[i][1]);
data.push(datum); data.push(datum);
} }
return data; return data;

View File

@ -7,8 +7,8 @@
<meta name="description" content="Track current and historical gold price trends for the World of Warcraft (WoW) in game token, including the US, EU, TW, and KR regions. Prices updated every minute. Simple, quick, and easy info, no ads or tracking, ever."> <meta name="description" content="Track current and historical gold price trends for the World of Warcraft (WoW) in game token, including the US, EU, TW, and KR regions. Prices updated every minute. Simple, quick, and easy info, no ads or tracking, ever.">
<link rel="preconnect" href="https://data.wowtoken.app"> <link rel="preconnect" href="https://data.wowtoken.app">
<link rel="dns-prefetch" href="https://data.wowtoken.app"> <link rel="dns-prefetch" href="https://data.wowtoken.app">
<link rel="preload" href="https://data.wowtoken.app/token/current.json" as="fetch" type="application/json" crossorigin="anonymous"> <link rel="preload" href="https://data.wowtoken.app/v2/current/retail.json" as="fetch" type="application/json" crossorigin="anonymous">
<link rel="preload" href="https://data.wowtoken.app/token/history/us/72h.json" as="fetch" type="application/json" crossorigin="anonymous"> <link rel="preload" href="https://data.wowtoken.app/v2/relative/retail/us/72h.json" as="fetch" type="application/json" crossorigin="anonymous">
</head> </head>
<body> <body>
<div class="flex-container"> <div class="flex-container">
@ -59,9 +59,15 @@
<label for="aggregate">Smoothing Function:</label> <label for="aggregate">Smoothing Function:</label>
<select name="aggregate" id="aggregate"> <select name="aggregate" id="aggregate">
<option id='agg_none' value="none">None</option> <option id='agg_none' value="none">None</option>
<option id='agg_davg' value="daily_mean">Daily Average</option> <option id='agg_davg' value="avg">Daily Average</option>
</select> </select>
</fieldset> </fieldset>
<fieldset id="period-overlay-options">
<label for="period-overlay" id="period-overlay-label">
Overlay previous <em id="period-time">0 hours</em> on current period:
</label>
<input type="checkbox" id="period-overlay" name="period-overlay">
</fieldset>
<fieldset id="y-start-options"> <fieldset id="y-start-options">
<label for="y-start">Start y-axis at 0:</label> <label for="y-start">Start y-axis at 0:</label>
<input type="checkbox" id="y-start" name="y-start"/> <input type="checkbox" id="y-start" name="y-start"/>

View File

@ -6,6 +6,7 @@ import fetchData from "./fetchData";
import {updateHighTime} from "./highTime"; import {updateHighTime} from "./highTime";
import {updateLowTime} from "./lowTime"; import {updateLowTime} from "./lowTime";
import {addLoader, removeLoader} from "./loader"; import {addLoader, removeLoader} from "./loader";
import {allowOverlay, forceOverlayOff, isOverlaySelected} from "./overlay";
import TokenChart from "./tokenChart"; import TokenChart from "./tokenChart";
import Datum from "./datum"; import Datum from "./datum";
@ -44,11 +45,11 @@ async function updateTokens(data) {
} }
async function updateRegionalToken(region, data) { async function updateRegionalToken(region, data) {
if (currentPriceHash[region] !== data['price_data'][region]) { if (currentPriceHash[region] !== data[region][1]) {
currentPriceHash[region] = data['price_data'][region]; currentPriceHash[region] = data[region][1];
if (region === currentRegionSelection) { if (region === currentRegionSelection) {
formatToken(); formatToken();
datum = new Datum(Date.parse(data['update_times'][region]), data['price_data'][region]); datum = new Datum(Date.parse(data[region][0]), data[region][1]);
if (currentAggregateSelection === 'none' && chart.active()) { if (currentAggregateSelection === 'none' && chart.active()) {
await chart.addDataToChart(datum); await chart.addDataToChart(datum);
} }
@ -66,7 +67,6 @@ async function updateRegionPreference(newRegion) {
currentRegionSelection = newRegion; currentRegionSelection = newRegion;
} }
formatToken(); formatToken();
chart = new TokenChart();
await pullChartData(); await pullChartData();
} }
@ -76,7 +76,12 @@ async function updateTimePreference(newTime) {
addLoader(); addLoader();
currentTimeSelection = newTime; currentTimeSelection = newTime;
} }
chart = new TokenChart(); if (newTime === "all") {
forceOverlayOff();
}
else {
allowOverlay();
}
await pullChartData(); await pullChartData();
updateHighTime(); updateHighTime();
updateLowTime(); updateLowTime();
@ -88,7 +93,6 @@ async function updateAggregatePreference(newAggregate) {
addLoader(); addLoader();
currentAggregateSelection = newAggregate; currentAggregateSelection = newAggregate;
} }
chart = new TokenChart();
await pullChartData(); await pullChartData();
} }
@ -109,16 +113,28 @@ function toggleStartYAtZero(){
chart.toggleYStart(startYAtZero); chart.toggleYStart(startYAtZero);
} }
async function toggleOverlay() {
await chart.destroyChart();
addLoader();
await pullChartData();
}
async function pullChartData() { async function pullChartData() {
chartData[currentRegionSelection] = await fetchData(currentRegionSelection, currentTimeSelection, currentAggregateSelection); let timeSelection = currentTimeSelection;
if (isOverlaySelected()) {
let timeDigits = parseInt(timeSelection.slice(0, timeSelection.length - 1)) * 2;
let timeUnit = timeSelection.slice(timeSelection.length - 1);
timeSelection = `${timeDigits}${timeUnit}`;
}
chartData[currentRegionSelection] = await fetchData(currentRegionSelection, timeSelection, currentAggregateSelection);
if (!chart.active()) { if (!chart.active()) {
await chart.createChart(currentRegionSelection, currentTimeSelection, startYAtZero, chartData[currentRegionSelection]); await chart.createChart(currentRegionSelection, currentTimeSelection, startYAtZero, chartData[currentRegionSelection]);
} }
else { else {
for (let i = 0; i < chartData[currentRegionSelection].length; i++) { for (let i = 0; i < chartData[currentRegionSelection].length; i++) {
await chart.addDataToChart(chartData[currentRegionSelection][i]); await chart.addDataToChart(chartData[currentRegionSelection][i]);
console.warn("This should never hit, and should be okay to remove");
} }
console.warn("This should never hit, and should be okay to remove");
} }
removeLoader(); removeLoader();
} }
@ -151,6 +167,9 @@ function detectTimeQuery(urlSearchParams) {
const validTimes = ['72h', '168h', '336h', '720h', '30d', '2190h', '90d', '1y', '2y', '6m', 'all']; const validTimes = ['72h', '168h', '336h', '720h', '30d', '2190h', '90d', '1y', '2y', '6m', 'all'];
if (validTimes.includes(urlSearchParams.get('time').toLowerCase())) { if (validTimes.includes(urlSearchParams.get('time').toLowerCase())) {
currentTimeSelection = urlSearchParams.get('time').toLowerCase(); currentTimeSelection = urlSearchParams.get('time').toLowerCase();
if (currentTimeSelection === 'all') {
forceOverlayOff();
}
let timeDDL = document.getElementById('time'); let timeDDL = document.getElementById('time');
for (let i = 0; i < timeDDL.options.length; i++) { for (let i = 0; i < timeDDL.options.length; i++) {
if (timeDDL.options[i].value === currentTimeSelection) { if (timeDDL.options[i].value === currentTimeSelection) {
@ -165,9 +184,13 @@ function detectTimeQuery(urlSearchParams) {
} }
function detectAggregateQuery(urlSearchParams) { function detectAggregateQuery(urlSearchParams) {
const validOperations = ['none', 'daily_mean']; const validOperations = ['none', 'daily_mean', 'avg'];
if (validOperations.includes(urlSearchParams.get('aggregate').toLowerCase())) { if (validOperations.includes(urlSearchParams.get('aggregate').toLowerCase())) {
currentAggregateSelection = urlSearchParams.get('aggregate').toLowerCase(); currentAggregateSelection = urlSearchParams.get('aggregate').toLowerCase();
// For backwards compatibility
if (currentAggregateSelection === 'daily_mean') {
currentAggregateSelection = 'avg';
}
let aggregateDDL = document.getElementById('aggregate'); let aggregateDDL = document.getElementById('aggregate');
for (let i = 0; i < aggregateDDL.options.length; i++) { for (let i = 0; i < aggregateDDL.options.length; i++) {
if (aggregateDDL.options[i].value === currentAggregateSelection) { if (aggregateDDL.options[i].value === currentAggregateSelection) {
@ -245,6 +268,8 @@ function registerEventHandles() {
registerAdvancedHandlers(); registerAdvancedHandlers();
} }
// TODO: These need to be moved out into probably tokenChart if not other files
function registerAdvancedHandlers() { function registerAdvancedHandlers() {
document.getElementById('enable-advanced').addEventListener('change', () => { document.getElementById('enable-advanced').addEventListener('change', () => {
toggleAdvancedSetting(); toggleAdvancedSetting();
@ -252,6 +277,9 @@ function registerAdvancedHandlers() {
document.getElementById('y-start').addEventListener('change', () => { document.getElementById('y-start').addEventListener('change', () => {
toggleStartYAtZero(); toggleStartYAtZero();
}) })
document.getElementById('period-overlay').addEventListener('change', () => {
toggleOverlay();
})
} }
function registerCopyHandlers() { function registerCopyHandlers() {

27
src/overlay.js Normal file
View File

@ -0,0 +1,27 @@
function isOverlaySelected() {
return document.getElementById('period-overlay').checked;
}
function getOverlayTime() {
return document.getElementById("time").selectedOptions[0].innerText;
}
function setOverlayLabelTime() {
const currentTime = document.getElementById("time").selectedOptions[0].innerText;
let overlayTimeLabel = document.getElementById("period-time");
overlayTimeLabel.innerText = currentTime.toLocaleString();
}
function forceOverlayOff(){
const overlaySetting = document.getElementById("period-overlay");
const periodOverlayField = document.getElementById("period-overlay-options");
overlaySetting.checked = false;
periodOverlayField.style.display = 'none';
}
function allowOverlay(){
const periodOverlayField = document.getElementById("period-overlay-options");
periodOverlayField.style.display = 'flex';
}
export {isOverlaySelected, getOverlayTime, setOverlayLabelTime, forceOverlayOff, allowOverlay};

View File

@ -24,6 +24,7 @@ Chart.register(
import {updateHighVal} from "./highTime"; import {updateHighVal} from "./highTime";
import {updateLowVal} from "./lowTime"; import {updateLowVal} from "./lowTime";
import {isOverlaySelected, getOverlayTime, setOverlayLabelTime} from "./overlay";
function lookupTimeUnit(query){ function lookupTimeUnit(query){
const lookup = { const lookup = {
@ -36,6 +37,24 @@ function lookupTimeUnit(query){
return lookup[query.charAt(query.length - 1)] return lookup[query.charAt(query.length - 1)]
} }
function timeDeltaInMilliseconds(time) {
let timeDigits = (parseInt(time.slice(0, time.length - 1))).toFixed(0);
let timeUnit = time.slice(time.length - 1);
switch (timeUnit) {
case 'h':
return timeDigits * (60 * 60) * 1000;
case 'd':
return timeDigits * (24 * 60 * 60) * 1000;
case 'm':
return (timeDigits * (30.437 * 24 * 60 * 60)).toFixed(0) * 1000;
case 'y':
return (timeDigits * (365.25 * 24 * 60 * 60)).toFixed(0) * 1000;
case 'l':
console.warn("This path should not happen, this warning is an error in logic")
}
}
export default class TokenChart { export default class TokenChart {
constructor() { constructor() {
this._context = document.getElementById("token-chart").getContext('2d'); this._context = document.getElementById("token-chart").getContext('2d');
@ -54,9 +73,104 @@ export default class TokenChart {
return this._lowDatum; return this._lowDatum;
} }
async createChart(region, time, yLevel, data) { async #newChart(chartConfig) {
this._chart = new Chart(this._context, chartConfig);
}
async #createOverlayChart(region, time, yLevel, data){
const chartData = []; const chartData = [];
let lateUpdateData = this._lastDatum; const overlayData = [];
const overlayDelta = timeDeltaInMilliseconds(time);
for (let i = 0; i < data.length; i++) {
const originalDate = data[i].getX();
if (i < (data.length / 2)) {
overlayData.push({
x: new Date(originalDate.getTime() + overlayDelta),
y: data[i].getY(),
});
}
else {
this._lastDatum = data[i];
if (this._highDatum === null || this._lastDatum.getPrice() > this._highDatum.getPrice()) {
this._highDatum = data[i];
}
if (this._lowDatum === null || this._lowDatum.getPrice() > this._lastDatum.getPrice()) {
this._lowDatum = data[i];
}
chartData.push({
x: data[i].getX(),
y: data[i].getY(),
})
}
}
const chartConfig = {
type: 'line',
data: {
datasets: [
{
borderColor: 'gold',
label: region.toUpperCase() + " WoW Token Price",
data: chartData,
cubicInterpolationMode: 'monotone',
pointRadius: 0
},
{
borderColor: 'red',
label: `Previous ${getOverlayTime()} ${region.toUpperCase()} WoW Token Price`,
data: overlayData,
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,
}
}
}
},
}
}
await this.#newChart(chartConfig)
}
async #createNormalChart(region, time, yLevel, data) {
const chartData = [];
for (let i = 0; i < data.length; i++) { for (let i = 0; i < data.length; i++) {
this._lastDatum = data[i]; this._lastDatum = data[i];
@ -74,10 +188,7 @@ export default class TokenChart {
}) })
} }
updateHighVal(this.highDatum); const chartConfig = {
updateLowVal(this.lowDatum);
this._chart = new Chart(this._context, {
type: 'line', type: 'line',
data: { data: {
datasets: [{ datasets: [{
@ -123,7 +234,25 @@ export default class TokenChart {
} }
}, },
} }
}); }
await this.#newChart(chartConfig)
}
async createChart(region, time, yLevel, data) {
let lateUpdateData = this._lastDatum;
if (isOverlaySelected()) {
await this.#createOverlayChart(region, time, yLevel, data)
}
else {
await this.#createNormalChart(region, time, yLevel, data)
}
setOverlayLabelTime();
updateHighVal(this.highDatum);
updateLowVal(this.lowDatum);
if (this._lateUpdate) { if (this._lateUpdate) {
if (this._lastDatum.getPrice() !== lateUpdateData.getPrice() && if (this._lastDatum.getPrice() !== lateUpdateData.getPrice() &&
@ -160,12 +289,7 @@ export default class TokenChart {
this._lowDatum = datum; this._lowDatum = datum;
updateLowVal(this.lowDatum); updateLowVal(this.lowDatum);
} }
this._chart.data.datasets.forEach((dataset) => { this._chart.data.datasets[0].data.push(datum);
dataset.data.push({
x: datum.getX(),
y: datum.getY(),
})
});
this._chart.update(); this._chart.update();
} }

View File

@ -1,8 +1,12 @@
export default function urlBuilder(currentRegionSelection, currentTimeSelection, currentAggregateSelection) { export default function urlBuilder(currentRegionSelection, currentTimeSelection, currentAggregateSelection) {
let url = "https://data.wowtoken.app/token/history/"; let url = "https://data.wowtoken.app/v2/";
if (currentAggregateSelection !== 'none') { if (currentAggregateSelection !== '' && currentAggregateSelection !== 'none'){
url += `${currentAggregateSelection}/` url += `math/${currentAggregateSelection}/retail/`
} }
url += `${currentRegionSelection}/${currentTimeSelection}.json` else {
url += `relative/retail/`
}
url += `${currentRegionSelection}/${currentTimeSelection}.json`;
return url; return url;
} }