MediaWiki:Gadget-GECharts-sandbox.js: Difference between revisions
Jump to navigation
Jump to search
Content added Content deleted
No edit summary |
No edit summary |
||
Line 27: | Line 27: | ||
* Cache mw.config variables |
* Cache mw.config variables |
||
*/ |
*/ |
||
function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); } |
|||
var conf = mw.config.get([ |
|||
var conf = mw.config.get(['wgNamespaceNumber', 'wgPageName', 'wgTitle', 'wgSiteName']), |
|||
'wgNamespaceNumber', |
|||
// Are we on OSRS? Impacts selectors and volume labels / multipliers |
|||
'wgPageName', |
|||
isOSRS = conf.wgSiteName == "RuneRealm Wiki", |
|||
'wgTitle', |
|||
// Volume label depends on which wiki we're on |
|||
'wgSiteName' |
|||
volumeLabel = "Daily volume", |
|||
]), |
|||
gameVersion = isOSRS ? 'osrs' : 'rs', |
|||
/** |
|||
// Are we on OSRS? Impacts selectors and volume labels / multipliers |
|||
* <doc> |
|||
isOSRS = conf.wgSiteName == "RuneRealm Wiki", |
|||
* |
|||
* @todo replace `_GEC` wih this |
|||
// Volume label depends on which wiki we're on |
|||
*/ |
|||
volumeLabel = "Daily volume", |
|||
gec = {}, |
|||
gameVersion = isOSRS ? 'osrs' : 'rs', |
|||
// @todo document each of these |
|||
_GEC = { |
|||
AIQueue: [], |
|||
AILoaded: [], |
|||
AIData: [], |
|||
addedData: [], |
|||
average: parseInt((location.hash.match(/#a=([^#]*)/) || [])[1], 10) || '', |
|||
urlCache: {}, |
|||
annotationsVisible: true |
|||
}, |
|||
/** |
|||
* Startup methods |
|||
*/ |
|||
self = { |
|||
/** |
|||
* Loads and implements any required dependencies |
|||
*/ |
|||
deps: function deps() { |
|||
if (!mw.loader.getState('rs.highcharts')) { |
|||
mw.loader.implement('rs.highcharts', ['https://code.highcharts.com/stock/highstock.js'], {}, {}); |
|||
} |
|||
if (!mw.loader.getState('rs.highcharts-annotations')) { |
|||
mw.loader.implement('rs.highcharts-annotations', ['https://code.highcharts.com/modules/annotations.js'], {}, {}); |
|||
} |
|||
mw.loader.using(['mediawiki.util', 'mediawiki.api', 'rs.highcharts', 'rs.highcharts-annotations', 'oojs-ui-core', 'oojs-ui.styles.icons-media'], self.init); |
|||
}, |
|||
/** |
|||
* Initial loading function |
|||
*/ |
|||
init: function init(req) { |
|||
window.Highcharts = req('rs.highcharts'); |
|||
req('rs.highcharts-annotations')(window.Highcharts); //load the annotations module, shouldn't need to store the return value anywhere |
|||
(function () { |
|||
var newhash = location.hash.replace(/\.([0-9a-f]{2})/gi, function (_, first) { |
|||
return String.fromCharCode(parseInt(first, 16)); |
|||
}).replace(/ /g, '_'); |
|||
if (newhash && newhash.match(/#[aiz]=/)) { |
|||
location.hash = newhash; |
|||
} |
|||
})(); |
|||
$('.GEdatachart').attr('id', function (c) { |
|||
return 'GEdatachart' + c; |
|||
}); |
|||
$('.GEdataprices').attr('id', function (c) { |
|||
return 'GEdataprices' + c; |
|||
}); |
|||
$('.GEChartBox').each(function (c) { |
|||
$(this).find('.GEChartItems').attr('id', 'GEChartItems' + c); |
|||
}); |
|||
Highcharts.setOptions({ |
|||
lang: { |
|||
// @todo can this be done with CSS? |
|||
resetZoom: null, |
|||
numericSymbols: ['K', 'M', 'B', 'T', 'Qd', 'Qt'] |
|||
} |
|||
}); |
|||
// globals to maintain javascript hrefs |
|||
window._GEC = _GEC; |
|||
window.popupChart = popupChart; |
|||
window.addItem = chart.addItem; |
|||
window.removeGraphItem = chart.removeItem; |
|||
self.buildPopup(); |
|||
self.setupCharts(); |
|||
}, |
|||
/** |
/** |
||
* <doc> |
* <doc> |
||
* |
|||
* @todo replace `_GEC` wih this |
|||
*/ |
*/ |
||
makeOOUI: function makeOOUI(c) { |
|||
gec = {}, |
|||
var averageRangeInput, addItemInput, submitButton, resetButton, annotationsToggle, fieldset, permalink; |
|||
averageRangeInput = new OO.ui.NumberInputWidget({ |
|||
// @todo document each of these |
|||
min: 1, |
|||
value: 30, |
|||
id: 'average' + c |
|||
}); |
|||
averageRangeInput.$element.data('ooui-elem', averageRangeInput); |
|||
addedData: [], |
|||
addItemInput = new OO.ui.TextInputWidget({ |
|||
average: parseInt((location.hash.match(/#a=([^#]*)/) || [])[1], 10) || '', |
|||
id: 'extraItem' + c |
|||
}); |
|||
annotationsVisible: true |
|||
addItemInput.$element.data('ooui-elem', addItemInput); |
|||
submitButton = new OO.ui.ButtonInputWidget({ |
|||
label: 'Submit', |
|||
flags: ['primary', 'progressive'] |
|||
}); |
|||
resetButton = new OO.ui.ButtonInputWidget({ |
|||
label: 'Reset' |
|||
}); |
|||
permalink = new OO.ui.ButtonInputWidget({ |
|||
label: 'Permanent link', |
|||
title: 'Permanent link to the current chart settings and items. Right click to copy the url.', |
|||
id: 'GEPermLink' + c |
|||
}); |
|||
permalink.$element.data('ooui-elem', permalink); |
|||
permalink.setData('/w/RuneScape:Grand_Exchange_Market_Watch/Chart'); |
|||
permalink.on('click', function () { |
|||
window.open(permalink.getData(), '_blank'); |
|||
}); |
|||
averageRangeInput.on('enter', function () { |
|||
addItem(c); |
|||
}); |
|||
addItemInput.on('enter', function () { |
|||
addItem(c); |
|||
}); |
|||
submitButton.on('click', function () { |
|||
addItem(c); |
|||
}); |
|||
resetButton.on('click', function () { |
|||
addItemInput.setValue(''); |
|||
averageRangeInput.setValue(30); |
|||
}); |
|||
annotationsToggle = new OO.ui.ToggleSwitchWidget({ |
|||
value: true |
|||
}); |
|||
annotationsToggle.on('change', function () { |
|||
window._GEC.annotationsVisible = annotationsToggle.getValue(); |
|||
for (var i = 0; i < window._GEC['chart' + c].annotations.length; i++) { |
|||
window._GEC['chart' + c].annotations[i].setVisibility(window._GEC.annotationsVisible); |
|||
} |
|||
}); |
|||
fieldset = new OO.ui.FieldsetLayout(); |
|||
fieldset.addItems([new OO.ui.FieldLayout(averageRangeInput, { |
|||
label: 'Average (days)' |
|||
}), new OO.ui.FieldLayout(addItemInput, { |
|||
label: 'Add new item' |
|||
}), new OO.ui.HorizontalLayout({ |
|||
items: [submitButton, resetButton, permalink] |
|||
}), new OO.ui.FieldLayout(annotationsToggle, { |
|||
label: 'Show annotations?' |
|||
})]); |
|||
fieldset.$element.css('width', '50%'); |
|||
return fieldset.$element; |
|||
}, |
|||
buildPopup: function buildPopup() { |
|||
var close; |
|||
close = new OO.ui.ButtonWidget({ |
|||
icon: 'close' |
|||
}); |
|||
close.on('click', function () { |
|||
popupChart(false); |
|||
}); |
|||
$('body').append($('<div>').attr('id', 'GEchartpopup').css('display', 'none').append($('<div>').attr('id', 'closepopup').append(close.$element), self.makeOOUI('popup'), $('<div>').attr('id', 'addedItemspopup'), $('<div>').attr('id', 'GEpopupchart'))); |
|||
}, |
}, |
||
/** |
/** |
||
* |
* <doc> |
||
*/ |
*/ |
||
setupCharts: function setupCharts() { |
|||
self = { |
|||
$('div.GEdatachart').each(function (c) { |
|||
/** |
|||
var $dataPrices = $('#GEdataprices' + c), |
|||
* Loads and implements any required dependencies |
|||
$dataChart = $('#GEdatachart' + c), |
|||
*/ |
|||
dataItem = $dataPrices.attr('data-item'), |
|||
deps: function () { |
|||
isSmall = $dataChart.hasClass('smallChart'), |
|||
isMedium = $dataChart.hasClass('mediumChart'), |
|||
isIndexChart = /index/i.test(dataItem), |
|||
selector = isOSRS ? '.infobox *, .infobar *, .infobox-switch-resources.infobox-resources-Infobox_Item *' : '.infobox *, .infobar *, .rsw-infobox *, .infobox-switch-resources.infobox-resources-Infobox_Item *', |
|||
['https://code.highcharts.com/stock/highstock.js'], |
|||
isInfobox = $dataPrices.is(selector), |
|||
itemName = dataItem || conf.wgTitle.split('/')[0], |
|||
dataList, |
|||
yAxis, |
|||
zoom; |
|||
if (!$dataPrices.length) { |
|||
return; |
|||
} |
|||
// setting up the form and chart elements |
|||
if (!mw.loader.getState('rs.highcharts-annotations')) { |
|||
if (!isSmall && !isMedium) { |
|||
$dataChart.before(self.makeOOUI(c), $('<div>').attr('id', 'addedItems' + c)); |
|||
'rs.highcharts-annotations', |
|||
} |
|||
['https://code.highcharts.com/modules/annotations.js'], |
|||
getData(c, isSmall, isMedium, undefined, function (data) { |
|||
var dataList = data[0]; |
|||
var yAxis = data[1]; |
|||
if (itemName.toLowerCase() !== 'blank') { |
|||
mw.loader.using(['mediawiki.util', 'mediawiki.api', 'rs.highcharts', 'rs.highcharts-annotations', 'oojs-ui-core', 'oojs-ui.styles.icons-media'], self.init); |
|||
zoom = parseInt((location.hash.match(/#z=([^#]*)/) || [])[1]); |
|||
}, |
|||
zoom = zoom && zoom <= 6 && zoom >= 0 ? zoom - 1 : zoom === 0 ? 0 : 2; |
|||
} |
|||
var enlarge = new OO.ui.ButtonWidget({ |
|||
label: 'Enlarge chart', |
|||
icon: 'fullScreen', |
|||
id: 'gec-enlarge-' + c |
|||
}); |
|||
enlarge.$element.css("font-size", "13px"); |
|||
// @todo this doesn't do anything on small charts |
|||
/** |
|||
// is it supposed to? |
|||
//var zoomOut = '<a href="javascript:_GEC.chart' + c + '.zoomOut();" style="text-decoration:underline;color:inherit;font-size:inherit;">Zoom out</a>'; |
|||
*/ |
|||
init: function (req) { |
|||
window.Highcharts = req('rs.highcharts'); |
|||
req('rs.highcharts-annotations')(window.Highcharts); //load the annotations module, shouldn't need to store the return value anywhere |
|||
(function () { |
|||
var newhash = location.hash |
|||
.replace(/\.([0-9a-f]{2})/gi, function (_, first) { |
|||
return String.fromCharCode(parseInt(first, 16)); |
|||
}) |
|||
.replace(/ /g, '_'); |
|||
if (newhash && newhash.match(/#[aiz]=/)) { |
|||
location.hash = newhash; |
|||
} |
|||
}()); |
|||
//generating the chart |
|||
$('.GEdatachart').attr('id', function (c) { |
|||
_GEC['chart' + c] = new Highcharts.StockChart({ |
|||
chart: { |
|||
renderTo: 'GEdatachart' + c, |
|||
backgroundColor: 'white', |
|||
plotBackgroundColor: 'white', |
|||
zoomType: '', |
|||
//height: isSmall?210:null, |
|||
$(this).find('.GEChartItems').attr('id', 'GEChartItems' + c); |
|||
events: { |
|||
redraw: function redraw() { |
|||
_GEC.thisid = this.renderTo.id.replace('GEdatachart', '').replace('GEpopupchart', 'popup'); |
|||
Highcharts.setOptions({ |
|||
setTimeout(function () { |
|||
setChartExtremes(_GEC.thisid); |
|||
}, 0); |
|||
numericSymbols: ['K', 'M', 'B', 'T', 'Qd', 'Qt'], |
|||
} |
} |
||
} |
} |
||
}, |
|||
legend: { |
|||
enabled: !isSmall && !isMedium, |
|||
backgroundColor: 'white', |
|||
align: 'right', |
|||
layout: 'vertical', |
|||
verticalAlign: 'top', |
|||
y: 85 |
|||
}, |
|||
responsive: { |
|||
rules: [{ |
|||
condition: { |
|||
//maxWidth: 500 |
|||
}, |
|||
chartOptions: { |
|||
legend: { |
|||
var averageRangeInput, addItemInput, submitButton, resetButton, annotationsToggle, fieldset, permalink; |
|||
align: 'center', |
|||
verticalAlign: 'bottom', |
|||
layout: 'horizontal' |
|||
} |
|||
}); |
|||
averageRangeInput.$element.data('ooui-elem', averageRangeInput); |
|||
addItemInput = new OO.ui.TextInputWidget({ |
|||
id: 'extraItem' + c |
|||
}); |
|||
addItemInput.$element.data('ooui-elem', addItemInput); |
|||
submitButton = new OO.ui.ButtonInputWidget({ |
|||
label: 'Submit', |
|||
flags: ['primary', 'progressive'] |
|||
}); |
|||
resetButton = new OO.ui.ButtonInputWidget({ |
|||
label: 'Reset' |
|||
}); |
|||
permalink = new OO.ui.ButtonInputWidget({ |
|||
label: 'Permanent link', |
|||
title: 'Permanent link to the current chart settings and items. Right click to copy the url.', |
|||
id: 'GEPermLink' + c |
|||
}); |
|||
permalink.$element.data('ooui-elem', permalink); |
|||
permalink.setData('/w/RuneScape:Grand_Exchange_Market_Watch/Chart'); |
|||
permalink.on('click', function () { |
|||
window.open(permalink.getData(), '_blank'); |
|||
}); |
|||
averageRangeInput.on('enter', function () { |
|||
addItem(c); |
|||
}); |
|||
addItemInput.on('enter', function () { |
|||
addItem(c); |
|||
}); |
|||
submitButton.on('click', function () { |
|||
addItem(c); |
|||
}); |
|||
resetButton.on('click', function () { |
|||
addItemInput.setValue(''); |
|||
averageRangeInput.setValue(30); |
|||
}); |
|||
annotationsToggle = new OO.ui.ToggleSwitchWidget({ |
|||
value: true |
|||
}); |
|||
annotationsToggle.on('change', function(){ |
|||
window._GEC.annotationsVisible = annotationsToggle.getValue(); |
|||
for (var i = 0; i<window._GEC['chart'+c].annotations.length; i++) { |
|||
window._GEC['chart'+c].annotations[i].setVisibility(window._GEC.annotationsVisible); |
|||
} |
|||
}); |
|||
fieldset = new OO.ui.FieldsetLayout(); |
|||
fieldset.addItems([ |
|||
new OO.ui.FieldLayout(averageRangeInput, {label: 'Average (days)'}), |
|||
new OO.ui.FieldLayout(addItemInput, {label: 'Add new item'}), |
|||
new OO.ui.HorizontalLayout({items: [ |
|||
submitButton, |
|||
resetButton, |
|||
permalink |
|||
]}), |
|||
new OO.ui.FieldLayout(annotationsToggle, {label: 'Show annotations?'}) |
|||
]); |
|||
fieldset.$element.css('width', '50%'); |
|||
return fieldset.$element; |
|||
}, |
|||
buildPopup: function () { |
|||
var close; |
|||
close = new OO.ui.ButtonWidget({ |
|||
icon: 'close' |
|||
}); |
|||
close.on('click', function () { |
|||
popupChart(false); |
|||
}); |
|||
$('body').append( |
|||
$('<div>') |
|||
.attr('id', 'GEchartpopup') |
|||
.css('display', 'none') |
|||
.append( |
|||
$('<div>') |
|||
.attr('id', 'closepopup') |
|||
.append(close.$element), |
|||
self.makeOOUI('popup'), |
|||
$('<div>') |
|||
.attr('id', 'addedItemspopup'), |
|||
$('<div>') |
|||
.attr('id', 'GEpopupchart') |
|||
) |
|||
); |
|||
}, |
|||
/** |
|||
* <doc> |
|||
*/ |
|||
setupCharts: function () { |
|||
$('div.GEdatachart').each(function (c) { |
|||
var $dataPrices = $('#GEdataprices' + c), |
|||
$dataChart = $('#GEdatachart' + c), |
|||
dataItem = $dataPrices.attr('data-item'), |
|||
isSmall = $dataChart.hasClass('smallChart'), |
|||
isMedium = $dataChart.hasClass('mediumChart'), |
|||
isIndexChart = /index/i.test(dataItem), |
|||
selector = isOSRS ? '.infobox *, .infobar *, .infobox-switch-resources.infobox-resources-Infobox_Item *' : '.infobox *, .infobar *, .rsw-infobox *, .infobox-switch-resources.infobox-resources-Infobox_Item *', |
|||
isInfobox = $dataPrices.is(selector), |
|||
itemName = dataItem || conf.wgTitle.split('/')[0], |
|||
dataList, |
|||
yAxis, |
|||
zoom; |
|||
if (!$dataPrices.length) { |
|||
return; |
|||
} |
} |
||
}] |
|||
}, |
|||
title: { |
|||
text: isSmall || isMedium ? isInfobox || isMedium ? enlarge.$element[0].outerHTML : itemName : 'Grand Exchange Market Watch', |
|||
$dataChart.before( |
|||
useHTML: true, |
|||
style: { |
|||
color: 'black', |
|||
fontSize: isSmall ? enlarge ? '13px' : '15px' : '18px' |
|||
} |
|||
}, |
|||
subtitle: { |
|||
text: isSmall ? isInfobox ? '' : enlarge.$element[0].outerHTML : itemName.toLowerCase() == 'blank' ? 'Historical chart' : itemName, |
|||
useHTML: true, |
|||
y: 50, |
|||
style: { |
|||
color: '#666', |
|||
fontSize: isSmall ? '13px' : '15px' |
|||
} |
|||
}, |
|||
rangeSelector: { |
|||
enabled: !isSmall && !isMedium, |
|||
selected: zoom, |
|||
inputBoxStyle: { |
|||
right: '15px', |
|||
display: isSmall || isMedium ? 'none' : 'block' |
|||
}, |
|||
inputStyle: { |
|||
width: '100px' |
|||
}, |
|||
inputDateFormat: "%e-%b-%Y", |
|||
buttonTheme: { |
|||
"class": 'zoomButton' |
|||
}, |
|||
buttons: [{ |
|||
type: 'month', |
|||
count: 1, |
|||
text: '1m' |
|||
}, { |
|||
type: 'month', |
|||
count: 2, |
|||
text: '2m' |
|||
}, { |
|||
type: 'month', |
|||
count: 3, |
|||
text: '3m' |
|||
}, { |
|||
type: 'month', |
|||
count: 6, |
|||
text: '6m' |
|||
}, { |
|||
type: 'year', |
|||
count: 1, |
|||
text: '1y' |
|||
}, { |
|||
type: 'all', |
|||
text: 'All' |
|||
}] |
|||
}, |
|||
plotOptions: { |
|||
series: { |
|||
enableMouseTracking: !isSmall, |
|||
dataGrouping: { |
|||
dateTimeLabelFormats: { |
|||
day: ['%A, %e %B %Y', '%A, %e %B', '-%A, %e %B %Y'], |
|||
week: ['Week from %A, %e %B %Y', '%A, %e %B', '-%A, %e %B %Y'], |
|||
month: ['%B %Y', '%B', '-%B %Y'], |
|||
year: ['%Y', '%Y', '-%Y'] |
|||
} |
|||
} |
} |
||
} |
|||
}, |
|||
tooltip: { |
|||
enabled: !isSmall, |
|||
valueDecimals: isIndexChart ? 2 : 0, |
|||
headerFormat: '<span style="font-size: 12px">{point.key}</span><br/>', |
|||
xDateFormat: "%A, %e %B %Y" |
|||
}, |
|||
navigator: { |
|||
xAxis: { |
|||
dateTimeLabelFormats: { |
|||
day: "%e-%b", |
|||
week: "%e-%b", |
|||
month: "%b-%Y", |
|||
year: "%Y" |
|||
}, |
|||
minTickInterval: 24 * 3600 * 1000 //1 day |
|||
}, |
|||
maskFill: 'none', |
|||
enabled: !(isSmall || isMedium) |
|||
}, |
|||
credits: { |
|||
enabled: false |
|||
}, |
|||
xAxis: [{ |
|||
lineColor: '#666', |
|||
tickColor: '#666', |
|||
dateTimeLabelFormats: { |
|||
day: "%e-%b", |
|||
week: "%e-%b", |
|||
month: "%b-%Y", |
|||
year: "%Y" |
|||
}, |
|||
minTickInterval: 24 * 3600 * 1000, |
|||
//1 day |
|||
scrollbar: { |
|||
enabled: false, |
|||
showFull: false |
|||
} |
|||
}], |
|||
yAxis: yAxis, |
|||
series: dataList, |
|||
colors: window.GEMWChartColors || ['#4572A7', '#AA4643', '#89A54E', '#80699B', '#3D96AE', '#DB843D', '#92A8CD', '#A47D7C', '#B5CA92'], |
|||
annotations: [data[2]] |
|||
}); |
|||
var items = ($('#GEChartItems' + c).html() || '').split(','); |
|||
var noAdd = []; |
|||
var i; |
|||
for (i = 0; i < items.length; i++) { |
|||
items[i] = items[i].trim(); |
|||
if (items[i]) { |
|||
addItem(c, items[i]); |
|||
} else { |
|||
noAdd.push(1); |
|||
} |
|||
} |
|||
if (items.length == noAdd.length && _GEC['chart' + c].series[0].name.toLowerCase() != 'blank') setChartRange(c); |
|||
//adjusting the axes extremes (initial load) |
|||
getData(c, isSmall, isMedium, undefined, function(data) { |
|||
setChartExtremes(c); |
|||
var yAxis = data[1]; |
|||
if (itemName.toLowerCase() !== 'blank') { |
|||
zoom = parseInt((location.hash.match(/#z=([^#]*)/) || [])[1]); |
|||
zoom = zoom && zoom <= 6 && zoom >= 0 ? |
|||
zoom - 1 : |
|||
(zoom === 0 ? |
|||
0 : |
|||
2); |
|||
} |
|||
var enlarge = new OO.ui.ButtonWidget( { |
|||
label: 'Enlarge chart', |
|||
icon: 'fullScreen', |
|||
id: 'gec-enlarge-' + c |
|||
} ); |
|||
enlarge.$element.css("font-size", "13px"); |
|||
// @todo this doesn't do anything on small charts |
|||
// is it supposed to? |
|||
//var zoomOut = '<a href="javascript:_GEC.chart' + c + '.zoomOut();" style="text-decoration:underline;color:inherit;font-size:inherit;">Zoom out</a>'; |
|||
//generating the chart |
|||
_GEC['chart' + c] = new Highcharts.StockChart({ |
|||
chart: { |
|||
renderTo: 'GEdatachart' + c, |
|||
backgroundColor: 'white', |
|||
plotBackgroundColor: 'white', |
|||
zoomType: '', |
|||
//height: isSmall?210:null, |
|||
events: { |
|||
redraw: function () { |
|||
_GEC.thisid = this.renderTo.id.replace('GEdatachart', '').replace('GEpopupchart', 'popup'); |
|||
setTimeout(function () { |
|||
setChartExtremes(_GEC.thisid); |
|||
}, 0); |
|||
} |
|||
}, |
|||
}, |
|||
legend: { |
|||
enabled: !isSmall && !isMedium, |
|||
backgroundColor: 'white', |
|||
align: 'right', |
|||
layout: 'vertical', |
|||
verticalAlign: 'top', |
|||
y: 85 |
|||
}, |
|||
responsive: { |
|||
rules: [{ |
|||
condition: { |
|||
//maxWidth: 500 |
|||
}, |
|||
chartOptions: { |
|||
legend: { |
|||
align: 'center', |
|||
verticalAlign: 'bottom', |
|||
layout: 'horizontal' |
|||
} |
|||
} |
|||
}] |
|||
}, |
|||
title: { |
|||
text: (isSmall || isMedium) ? ((isInfobox || isMedium) ? enlarge.$element[0].outerHTML : itemName) : 'Grand Exchange Market Watch', |
|||
useHTML: true, |
|||
style: { |
|||
color: 'black', |
|||
fontSize: isSmall ? (enlarge ? '13px' : '15px') : '18px', |
|||
}, |
|||
}, |
|||
subtitle: { |
|||
text: isSmall ? (isInfobox ? '' : enlarge.$element[0].outerHTML) : (itemName.toLowerCase() == 'blank' ? 'Historical chart' : itemName), |
|||
useHTML: true, |
|||
y: 50, |
|||
style: { |
|||
color: '#666', |
|||
fontSize: isSmall ? '13px' : '15px', |
|||
}, |
|||
}, |
|||
rangeSelector: { |
|||
enabled: !isSmall && !isMedium, |
|||
selected: zoom, |
|||
inputBoxStyle: { |
|||
right: '15px', |
|||
display: (isSmall || isMedium) ? 'none' : 'block' |
|||
}, |
|||
inputStyle: { |
|||
width: '100px', |
|||
}, |
|||
inputDateFormat: "%e-%b-%Y", |
|||
buttonTheme: { |
|||
class: 'zoomButton', |
|||
}, |
|||
buttons: [{ |
|||
type: 'month', |
|||
count: 1, |
|||
text: '1m' |
|||
}, { |
|||
type: 'month', |
|||
count: 2, |
|||
text: '2m' |
|||
}, { |
|||
type: 'month', |
|||
count: 3, |
|||
text: '3m' |
|||
}, { |
|||
type: 'month', |
|||
count: 6, |
|||
text: '6m' |
|||
}, { |
|||
type: 'year', |
|||
count: 1, |
|||
text: '1y' |
|||
}, { |
|||
type: 'all', |
|||
text: 'All' |
|||
}] |
|||
}, |
|||
plotOptions: { |
|||
series: { |
|||
enableMouseTracking: !isSmall, |
|||
dataGrouping: { |
|||
dateTimeLabelFormats: { |
|||
day: ['%A, %e %B %Y', '%A, %e %B', '-%A, %e %B %Y'], |
|||
week: ['Week from %A, %e %B %Y', '%A, %e %B', '-%A, %e %B %Y'], |
|||
month: ['%B %Y', '%B', '-%B %Y'], |
|||
year: ['%Y', '%Y', '-%Y'] |
|||
} |
|||
} |
|||
} |
|||
}, |
|||
tooltip: { |
|||
enabled: !isSmall, |
|||
valueDecimals: isIndexChart ? 2 : 0, |
|||
headerFormat: '<span style="font-size: 12px">{point.key}</span><br/>', |
|||
xDateFormat: "%A, %e %B %Y", |
|||
}, |
|||
navigator: { |
|||
xAxis: { |
|||
dateTimeLabelFormats: { |
|||
day: "%e-%b", |
|||
week: "%e-%b", |
|||
month: "%b-%Y", |
|||
year: "%Y", |
|||
}, |
|||
minTickInterval: 24 * 3600 * 1000, //1 day |
|||
}, |
|||
maskFill: 'none', |
|||
enabled: !(isSmall || isMedium) |
|||
}, |
|||
credits: { |
|||
enabled: false, |
|||
}, |
|||
xAxis: [{ |
|||
lineColor: '#666', |
|||
tickColor: '#666', |
|||
dateTimeLabelFormats: { |
|||
day: "%e-%b", |
|||
week: "%e-%b", |
|||
month: "%b-%Y", |
|||
year: "%Y", |
|||
}, |
|||
minTickInterval: 24 * 3600 * 1000, //1 day |
|||
scrollbar: { |
|||
enabled: false, |
|||
showFull: false |
|||
}, |
|||
}], |
|||
yAxis: yAxis, |
|||
series: dataList, |
|||
colors: window.GEMWChartColors || ['#4572A7', '#AA4643', '#89A54E', '#80699B', '#3D96AE', '#DB843D', '#92A8CD', '#A47D7C', '#B5CA92'], |
|||
annotations: [data[2]], |
|||
}); |
|||
var items = ($('#GEChartItems' + c).html() || '').split(','); |
|||
var noAdd = []; |
|||
var i; |
|||
for (i = 0; i < items.length; i++) { |
|||
items[i] = items[i].trim(); |
|||
if (items[i]) { |
|||
addItem(c, items[i]); |
|||
} else { |
|||
noAdd.push(1); |
|||
} |
|||
} |
|||
if (items.length == noAdd.length && _GEC['chart' + c].series[0].name.toLowerCase() != 'blank') setChartRange(c); |
|||
//adjusting the axes extremes (initial load) |
|||
setChartExtremes(c); |
|||
//loading the chart and additional price info when the page is ready |
|||
if (((conf.wgNamespaceNumber == 112 && conf.wgTitle.split('/')[1] == 'Data') || conf.wgPageName == 'RuneScape:Grand_Exchange_Market_Watch/Chart') && location.hash.match('#i=') !== null) { |
|||
var hash = location.hash; |
|||
items = decodeURIComponent((hash.match(/#i=([^#]*)/) || [])[1] || '').replace(/_/g, ' ').split(','); |
|||
for (i = 0; i < items.length; i++) { |
|||
if (items[i].match(/^\s*$/) === null) addItem(0, items[i]); |
|||
} |
|||
} |
|||
var $enlargeEle = $("#gec-enlarge-" + c); |
|||
if ($enlargeEle.length) { |
|||
$enlargeEle.on("click", function () { |
|||
popupChart(c); |
|||
}); |
|||
}; |
|||
}); |
|||
//loading the chart and additional price info when the page is ready |
|||
if ((conf.wgNamespaceNumber == 112 && conf.wgTitle.split('/')[1] == 'Data' || conf.wgPageName == 'RuneScape:Grand_Exchange_Market_Watch/Chart') && location.hash.match('#i=') !== null) { |
|||
var hash = location.hash; |
|||
items = decodeURIComponent((hash.match(/#i=([^#]*)/) || [])[1] || '').replace(/_/g, ' ').split(','); |
|||
for (i = 0; i < items.length; i++) { |
|||
if (items[i].match(/^\s*$/) === null) addItem(0, items[i]); |
|||
} |
|||
} |
|||
var $enlargeEle = $("#gec-enlarge-" + c); |
|||
if ($enlargeEle.length) { |
|||
$enlargeEle.on("click", function () { |
|||
popupChart(c); |
|||
}); |
}); |
||
} |
|||
; |
|||
}); |
|||
}); |
|||
} |
|||
}, |
|||
/** |
|||
* General helper methods |
|||
*/ |
|||
util = { |
|||
/** |
|||
* <doc> |
|||
* |
|||
* @todo replace with $.extend |
|||
* |
|||
* @param a {object} |
|||
* @param b {object} (optional) |
|||
* |
|||
* @return {object} |
|||
*/ |
|||
cloneObj: function cloneObj(a, b) { |
|||
if (_typeof(a) !== 'object') { |
|||
return ''; |
|||
} |
|||
if (_typeof(b) !== 'object') { |
|||
b = {}; |
|||
} |
|||
for (var key in a) { |
|||
if (a.hasOwnProperty(key)) { |
|||
b[key] = a[key]; |
|||
} |
} |
||
} |
|||
return b; |
|||
}, |
}, |
||
/** |
/** |
||
* Averages prices across a specified time interval |
|||
* General helper methods |
|||
* |
|||
* @param arr {array} Array of arrays, where each member of `arr` |
|||
* is in the format [time, price] |
|||
* Which is how we store the price data |
|||
* @example [x-coord, y-coord] |
|||
* @param amt {number} Interval to average across in days |
|||
* @param round {number} (optional) Number of decimal places to round to |
|||
* Defaults to 0 |
|||
* |
|||
* @return {array} Array of arrays, where each member of the return array |
|||
* is in the format [time, price] (as above) |
|||
* and |
|||
*/ |
*/ |
||
avg: function avg(arr, amt, round) { |
|||
util = { |
|||
amt = amt || arr.length; |
|||
// convert `round` into a number we can use for rounding |
|||
* <doc> |
|||
round = Math.pow(10, round || 0); |
|||
var avgs = [], |
|||
* @todo replace with $.extend |
|||
list = [], |
|||
i; |
|||
* @param b {object} (optional) |
|||
* |
|||
* @return {object} |
|||
*/ |
|||
cloneObj: function (a, b) { |
|||
if (typeof a !== 'object') { |
|||
return ''; |
|||
} |
|||
if (typeof b !== 'object') { |
|||
b = {}; |
|||
} |
|||
for (var key in a) { |
|||
if (a.hasOwnProperty(key)) { |
|||
b[key] = a[key]; |
|||
} |
|||
} |
|||
return b; |
|||
}, |
|||
/** |
|||
* Averages prices across a specified time interval |
|||
* |
|||
* @param arr {array} Array of arrays, where each member of `arr` |
|||
* is in the format [time, price] |
|||
* Which is how we store the price data |
|||
* @example [x-coord, y-coord] |
|||
* @param amt {number} Interval to average across in days |
|||
* @param round {number} (optional) Number of decimal places to round to |
|||
* Defaults to 0 |
|||
* |
|||
* @return {array} Array of arrays, where each member of the return array |
|||
* is in the format [time, price] (as above) |
|||
* and |
|||
*/ |
|||
avg: function (arr, amt, round) { |
|||
amt = amt || arr.length; |
|||
// convert `round` into a number we can use for rounding |
|||
round = Math.pow(10, round || 0); |
|||
var avgs = [], |
|||
list = [], |
|||
i; |
|||
// adds each price to `list` |
|||
// when `amt` is reached, average the contents of `list` |
|||
// |
|||
// each iteration after `amt` is reached averages the contents of `list` |
|||
// which is continuously being updated as each iteration |
|||
// after `amt` is reached replaces a member of `list` |
|||
// @example when `i` is 31 the current price replaces `list[1]` |
|||
// when `i` is 35 the current price replaces `list[5]` |
|||
for (i = 0; i < arr.length; i++) { |
|||
list[i % amt] = arr[i][1]; |
|||
if (i >= amt) { |
|||
avgs.push([ |
|||
// don't modify the time (y-coord) |
|||
arr[i][0], |
|||
Math.round((util.sum(list) / list.length) * round) / round |
|||
]); |
|||
} |
|||
} |
|||
return avgs; |
|||
}, |
|||
/** |
|||
* Finds the sum of numbers in an array |
|||
* Only called by `util.avg` |
|||
* |
|||
* @param arr {array} Array of number to find the sum of |
|||
* |
|||
* @return {number} Sum of the numbers in `arr` |
|||
*/ |
|||
sum: function (arr) { |
|||
var total = 0, |
|||
i; |
|||
for (i = 0; i < arr.length; i++) { |
|||
total += parseFloat(arr[i], 10); |
|||
} |
|||
return total; |
|||
}, |
|||
/** |
|||
* Rounds and formats numbers |
|||
* |
|||
* @example 12345 -> 12.3K |
|||
* @example 1234567 -> 1.2M |
|||
* @example 123456789012 -> 123.4M |
|||
* |
|||
* @param num {number|string} Number to format |
|||
* |
|||
* @return {string} Formatted number |
|||
*/ |
|||
toKMB: function (num) { |
|||
// strip commas from number string |
|||
// as `parseInt` will interpret them as a decimal separator |
|||
// pass numbers and string to `parseInt` to convert floats too |
|||
num = parseInt((typeof num === 'string' ? num.replace(/,/g, '') : num), 10); |
|||
var neg = num < 0 ? '-' : ''; |
|||
num = Math.abs(num); |
|||
// `1eX` is shorthand for `Math.pow( 10, X )` |
|||
if (num >= 1e10) { |
|||
num = Math.round(num / 1e8) / 10; |
|||
num += 'B'; |
|||
} else if (num >= 1e7) { |
|||
num = Math.round(num / 1e5) / 10; |
|||
num += 'M'; |
|||
} else if (num >= 1e4) { |
|||
num = Math.round(num / 100) / 10; |
|||
num += 'K'; |
|||
} |
|||
return rs.addCommas(neg + num); |
|||
}, |
|||
/** |
|||
* Capitalises first character of a string |
|||
* |
|||
* @source <https://stackoverflow.com/a/1026087> |
|||
* |
|||
* @param str {string} |
|||
* |
|||
* @return {string} |
|||
*/ |
|||
ucFirst: function (str) { |
|||
return str.charAt(0).toUpperCase() + str.slice(1); |
|||
}, |
|||
/** |
|||
* Sort data points in the graph data before passing it to the charts api |
|||
*/ |
|||
sortPoints: function (a, b) { |
|||
a = a.replace(/'/g, '').split(':')[0]; |
|||
b = b.replace(/'/g, '').split(':')[0]; |
|||
// adds each price to `list` |
|||
// when `amt` is reached, average the contents of `list` |
|||
// |
|||
// each iteration after `amt` is reached averages the contents of `list` |
|||
// which is continuously being updated as each iteration |
|||
// after `amt` is reached replaces a member of `list` |
|||
// @example when `i` is 31 the current price replaces `list[1]` |
|||
// when `i` is 35 the current price replaces `list[5]` |
|||
for (i = 0; i < arr.length; i++) { |
|||
list[i % amt] = arr[i][1]; |
|||
if (i >= amt) { |
|||
avgs.push([ |
|||
// don't modify the time (y-coord) |
|||
arr[i][0], Math.round(util.sum(list) / list.length * round) / round]); |
|||
} |
} |
||
} |
|||
return avgs; |
|||
}, |
}, |
||
/** |
|||
* Finds the sum of numbers in an array |
|||
* Only called by `util.avg` |
|||
* |
|||
* @param arr {array} Array of number to find the sum of |
|||
* |
|||
* @return {number} Sum of the numbers in `arr` |
|||
*/ |
|||
sum: function sum(arr) { |
|||
var total = 0, |
|||
i; |
|||
for (i = 0; i < arr.length; i++) { |
|||
total += parseFloat(arr[i], 10); |
|||
} |
|||
return total; |
|||
}, |
|||
/** |
|||
* Rounds and formats numbers |
|||
* |
|||
* @example 12345 -> 12.3K |
|||
* @example 1234567 -> 1.2M |
|||
* @example 123456789012 -> 123.4M |
|||
* |
|||
* @param num {number|string} Number to format |
|||
* |
|||
* @return {string} Formatted number |
|||
*/ |
|||
toKMB: function toKMB(num) { |
|||
// strip commas from number string |
|||
// as `parseInt` will interpret them as a decimal separator |
|||
// pass numbers and string to `parseInt` to convert floats too |
|||
num = parseInt(typeof num === 'string' ? num.replace(/,/g, '') : num, 10); |
|||
var neg = num < 0 ? '-' : ''; |
|||
num = Math.abs(num); |
|||
// `1eX` is shorthand for `Math.pow( 10, X )` |
|||
if (num >= 1e10) { |
|||
num = Math.round(num / 1e8) / 10; |
|||
num += 'B'; |
|||
} else if (num >= 1e7) { |
|||
num = Math.round(num / 1e5) / 10; |
|||
num += 'M'; |
|||
} else if (num >= 1e4) { |
|||
num = Math.round(num / 100) / 10; |
|||
num += 'K'; |
|||
} |
|||
return rs.addCommas(neg + num); |
|||
}, |
|||
/** |
/** |
||
* Capitalises first character of a string |
|||
* Chart methods |
|||
* |
|||
* @source <https://stackoverflow.com/a/1026087> |
|||
* |
|||
* @param str {string} |
|||
* |
|||
* @return {string} |
|||
*/ |
*/ |
||
ucFirst: function ucFirst(str) { |
|||
chart = { |
|||
return str.charAt(0).toUpperCase() + str.slice(1); |
|||
/** |
|||
}, |
|||
/** |
|||
* Sort data points in the graph data before passing it to the charts api |
|||
* @param id {string|number} |
|||
*/ |
|||
* @param match {string} is normally the 'line' that isn't an item's price data |
|||
sortPoints: function sortPoints(a, b) { |
|||
* such as average or volume |
|||
a = a.replace(/'/g, '').split(':')[0]; |
|||
b = b.replace(/'/g, '').split(':')[0]; |
|||
return a - b; |
|||
} |
|||
getSeriesIndex: function (id, match) { |
|||
}, |
|||
var chart = _GEC['chart' + id], |
|||
/** |
|||
series = chart.series, |
|||
* Chart methods |
|||
*/ |
|||
chart = { |
|||
/** |
|||
* <doc> |
|||
* |
|||
* @param id {string|number} |
|||
* @param match {string} is normally the 'line' that isn't an item's price data |
|||
* such as average or volume |
|||
* |
|||
* @return {number} |
|||
*/ |
|||
getSeriesIndex: function getSeriesIndex(id, match) { |
|||
var chart = _GEC['chart' + id], |
|||
series = chart.series, |
|||
i; |
|||
if (chart) { |
|||
for (i = 0; i < series.length; i++) { |
|||
if (series[i].name.match(match)) { |
|||
return i; |
|||
} |
|||
} |
|||
return -1; |
|||
} |
|||
// @todo what happens if !chart |
|||
}, |
|||
for (i = 0; i < series.length; i++) { |
|||
/** |
|||
if (series[i].name.match(match)) { |
|||
* Creates a URL with preset options |
|||
return i; |
|||
* |
|||
* @todo change to url params |
|||
* @todo document the individual params/options |
|||
* |
|||
* @param id {number|string} |
|||
* |
|||
* @return {string} |
|||
*/ |
|||
permLinkUrl: function permLinkUrl(id) { |
|||
var chart = _GEC['chart' + id], |
|||
xt = chart.xAxis[0].getExtremes(), |
|||
series = chart.series, |
|||
minDate = new Date(xt.min).toDateString().split(' ').slice(1).join('_'), |
|||
maxDate = new Date(xt.max).toDateString().split(' ').slice(1).join('_'), |
|||
inputAvg = $('#average' + id).data('ooui-elem').getNumericValue(), |
|||
urlHash = '#t=' + minDate + ',' + maxDate, |
|||
items = '', |
|||
i; |
|||
if (!isNaN(inputAvg)) { |
|||
urlHash += '#a=' + inputAvg; |
|||
} |
|||
for (i = 0; i < series.length; i++) { |
|||
if (series[i].name == 'Navigator' || series[i].name.match('average')) { |
|||
continue; |
|||
} |
|||
// separate items with commas |
|||
if (items) { |
|||
items += ','; |
|||
} |
|||
// @todo url encode this? |
|||
items += series[i].name.replace(/ /g, '_'); |
|||
}, |
|||
} |
|||
urlHash += '#i=' + items; |
|||
// @todo hide the redirect h2 |
|||
return '/w/RuneScape:Grand_Exchange_Market_Watch/Chart' + urlHash; |
|||
* Creates a URL with preset options |
|||
}, |
|||
/** |
|||
* @todo change to url params |
|||
* Add a new item to the chart |
|||
* |
|||
* @param i |
|||
* @param it {string} (optional) |
|||
*/ |
|||
addItem: function addItem(i, it) { |
|||
*/ |
|||
_GEC.chartid = i; |
|||
permLinkUrl: function (id) { |
|||
var OOUIextraItemPresent = $('#extraItem' + i).length > 0, |
|||
OOUIextraItem = $('#extraItem' + i).data('ooui-elem'), |
|||
xt = chart.xAxis[0].getExtremes(), |
|||
item = (it || '').trim() || OOUIextraItem.getValue(), |
|||
dataItems = ['#addedItems' + i + ' [data-item]', '#GEdataprices' + i + '[data-item]'], |
|||
minDate = (new Date(xt.min)) |
|||
$dataItems = $(dataItems.join(',')).map(function () { |
|||
.toDateString() |
|||
return $(this).attr('data-item').toLowerCase(); |
|||
}), |
|||
$addedItems = $('#addedItems' + i), |
|||
id, |
|||
maxDate = (new Date(xt.max)) |
|||
data, |
|||
series, |
|||
seriesIndex, |
|||
gecchartid = i, |
|||
index; |
|||
inputAvg = $('#average' + id).data('ooui-elem').getNumericValue(), |
|||
if (item && item.length) { |
|||
urlHash = '#t=' + minDate + ',' + maxDate, |
|||
index = -1; |
|||
for (var i2 = 0; i2 < _GEC.AIQueue.length; i2++) { |
|||
if (_GEC.AIQueue[i2] == item.toLowerCase()) { |
|||
index = i2; |
|||
break; |
|||
} |
|||
} |
|||
if ( |
|||
// @todo should a number passed to .get() |
|||
$dataItems.get().indexOf(item.toLowerCase()) !== -1 || index !== -1) { |
|||
if (!it) { |
|||
alert(item + ' is already in the graph.'); |
|||
} |
|||
if (OOUIextraItemPresent) { |
|||
OOUIextraItem.setValue(''); |
|||
} |
|||
return false; |
|||
} |
|||
if (OOUIextraItemPresent) { |
|||
OOUIextraItem.setDisabled(true); |
|||
} |
|||
$.get('/api.php', { |
|||
action: 'query', |
|||
prop: 'revisions', |
|||
rvprop: 'content', |
|||
format: 'json', |
|||
titles: 'Module:Exchange/' + util.ucFirst(item) |
|||
}).then(function (data, textStatus) { |
|||
var OOUIextraItem = $('#extraItem' + gecchartid).data('ooui-elem'), |
|||
pages = data.query.pages; |
|||
if (textStatus !== 'success') { |
|||
alert('An error occured while loading ' + item); |
|||
mw.log(data); |
|||
} |
|||
var matches = []; |
|||
var pageMissing = false; |
|||
if (pages[-1]) { |
|||
pageMissing = true; |
|||
} else { |
|||
var exchangeData = pages[Object.keys(pages)[0]].revisions[0]['*']; |
|||
matches = exchangeData.match(/itemId\D*(\d*)/); |
|||
if (matches.length !== 2) { |
|||
pageMissing = true; |
|||
} |
} |
||
} |
|||
// page not found |
|||
if (pageMissing) { |
|||
if (series[i].name == 'Navigator' || series[i].name.match('average')) { |
|||
if (OOUIextraItem.getValue().length) { |
|||
alert('The item ' + item + ' doesn\'t exist on our Grand Exchange database.'); |
|||
} |
|||
OOUIextraItem.setDisabled(false).setValue(''); |
|||
return false; |
|||
if (items) { |
|||
items += ','; |
|||
} |
|||
// @todo url encode this? |
|||
items += series[i].name.replace(/ /g, '_'); |
|||
} |
} |
||
_GEC.AILoaded.push(false); |
|||
if (_GEC.AIData.length && _GEC.AIQueue.length == _GEC.AILoaded.length) { |
|||
urlHash += '#i=' + items; |
|||
loadChartsQueueComplete(gecchartid); |
|||
} else if (!_GEC.AIData.length) { |
|||
setChartRange(gecchartid); |
|||
return '/w/RuneScape:Grand_Exchange_Market_Watch/Chart' + urlHash; |
|||
}, |
|||
/** |
|||
* Add a new item to the chart |
|||
* |
|||
* @param i |
|||
* @param it {string} (optional) |
|||
*/ |
|||
addItem: function (i, it) { |
|||
_GEC.chartid = i; |
|||
var OOUIextraItemPresent = $('#extraItem' + i).length > 0, |
|||
OOUIextraItem = $('#extraItem' + i).data('ooui-elem'), |
|||
item = (it || '').trim() || OOUIextraItem.getValue(), |
|||
dataItems = [ |
|||
'#addedItems' + i + ' [data-item]', |
|||
'#GEdataprices' + i + '[data-item]' |
|||
], |
|||
$dataItems = $(dataItems.join(',')).map(function () { |
|||
return $(this).attr('data-item').toLowerCase(); |
|||
}), |
|||
$addedItems = $('#addedItems' + i), |
|||
id, |
|||
data, |
|||
series, |
|||
seriesIndex, |
|||
gecchartid = i, |
|||
index; |
|||
if (item && item.length) { |
|||
index = -1; |
|||
for (var i2 = 0; i2 < _GEC.AIQueue.length; i2++) { |
|||
if (_GEC.AIQueue[i2] == item.toLowerCase()) { |
|||
index = i2; |
|||
break; |
|||
} |
|||
} |
|||
if ( |
|||
// @todo should a number passed to .get() |
|||
$dataItems.get().indexOf(item.toLowerCase()) !== -1 || |
|||
index !== -1 |
|||
) { |
|||
if (!it) { |
|||
alert(item + ' is already in the graph.'); |
|||
} |
|||
if (OOUIextraItemPresent) { |
|||
OOUIextraItem.setValue(''); |
|||
} |
|||
return false; |
|||
} |
|||
if (OOUIextraItemPresent) { |
|||
OOUIextraItem.setDisabled(true); |
|||
} |
|||
$.get( |
|||
'/api.php', |
|||
{ |
|||
action: 'query', |
|||
prop: 'revisions', |
|||
rvprop: 'content', |
|||
format: 'json', |
|||
titles: 'Module:Exchange/' + util.ucFirst(item) |
|||
} |
|||
).then(function(data, textStatus) { |
|||
var OOUIextraItem = $('#extraItem' + gecchartid).data('ooui-elem'), |
|||
pages = data.query.pages; |
|||
if (textStatus !== 'success') { |
|||
alert('An error occured while loading ' + item); |
|||
mw.log(data); |
|||
} |
|||
var matches = [] |
|||
var pageMissing = false; |
|||
if (pages[-1]) { |
|||
pageMissing = true; |
|||
} else { |
|||
var exchangeData = pages[Object.keys(pages)[0]] |
|||
.revisions[0]['*']; |
|||
matches = exchangeData.match(/itemId\D*(\d*)/); |
|||
if (matches.length !== 2) { |
|||
pageMissing = true; |
|||
} |
|||
} |
|||
// page not found |
|||
if (pageMissing) { |
|||
if (OOUIextraItem.getValue().length) { |
|||
alert('The item ' + item + ' doesn\'t exist on our Grand Exchange database.'); |
|||
OOUIextraItem.setDisabled(false).setValue(''); |
|||
return false; |
|||
} |
|||
_GEC.AILoaded.push(false); |
|||
if ( |
|||
_GEC.AIData.length && |
|||
_GEC.AIQueue.length == _GEC.AILoaded.length |
|||
) { |
|||
loadChartsQueueComplete(gecchartid); |
|||
} else if (!_GEC.AIData.length) { |
|||
setChartRange(gecchartid); |
|||
} |
|||
OOUIextraItem.setDisabled(false).setValue(''); |
|||
return false; |
|||
} |
|||
var itemId = matches[1]; |
|||
return $.getJSON("https://api.weirdgloop.org/exchange/history/" + gameVersion + "/all?compress=true&id=" + itemId); |
|||
}).then(function(data, textStatus) { |
|||
if (data === false) return; |
|||
$.getJSON(mw.util.getUrl('Module:Exchange/'+util.ucFirst(item)+'/annotations.json', {action:'raw', 'ctype':'application/json'})).then( |
|||
function(annodata){return annodata}, |
|||
function(){return []} //handles the page not existing |
|||
).then(function(annodata){ |
|||
_GEC.AIData.push({ |
|||
name: item, |
|||
data: Object.values(data)[0], |
|||
id: item, |
|||
gecchartid: gecchartid, |
|||
lineWidth: 2, |
|||
annotations: formatAnnoList(annodata, Object.values(data)[0]) |
|||
}); |
|||
_GEC.AILoaded.push(item); |
|||
if (getSeriesIndex(gecchartid, 'average') !== -1) { |
|||
_GEC['chart' + gecchartid] |
|||
.series[getSeriesIndex(gecchartid, 'average')] |
|||
.remove(); |
|||
} |
|||
if (_GEC.AIQueue.length === _GEC.AILoaded.length) { |
|||
// This is always true when only 1 item is being loaded. |
|||
loadChartsQueueComplete(gecchartid); |
|||
} |
|||
}); |
|||
}) |
|||
_GEC.AIQueue.push({item: item.toLowerCase(), chart: gecchartid}); |
|||
// @todo when does this happen |
|||
/* This happens when there are no further items added to the charts, i.e. when the original item is the only one. |
|||
This is indeed a flawed test, since it won't work on GEMW/C, where there is no original item in the chart. |
|||
This should be replaced with another test that also works on GEMW/C. |
|||
*/ |
|||
} else if ( |
|||
$addedItems.html().match(/^\s*$/) || |
|||
( |
|||
conf.wgPageName == 'RuneScape:Grand_Exchange_Market_Watch/Chart' && |
|||
$addedItems.find('a').length === 1 |
|||
) |
|||
) { |
|||
id = (i === 'popup' ? $('#GEchartpopup').attr('data-chartid') : i); |
|||
getData(id, false, false, i, function(data) { |
|||
series = _GEC['chart' + i].series; |
|||
seriesIndex = getSeriesIndex(i, 'average'); |
|||
//remove an average line if it already exists |
|||
if (seriesIndex !== -1) { |
|||
series[seriesIndex].remove(); |
|||
} |
|||
//add average line when there is only 1 item in the chart |
|||
_GEC['chart' + i].addSeries(data[0][1]); |
|||
}); |
|||
} |
} |
||
OOUIextraItem.setDisabled(false).setValue(''); |
|||
}, |
|||
return false; |
|||
} |
|||
var itemId = matches[1]; |
|||
return $.getJSON("https://api.weirdgloop.org/exchange/history/" + gameVersion + "/all?compress=true&id=" + itemId); |
|||
* |
|||
}).then(function (data, textStatus) { |
|||
if (data === false) return; |
|||
$.getJSON(mw.util.getUrl('Module:Exchange/' + util.ucFirst(item) + '/annotations.json', { |
|||
loadQueueComplete: function (cin, addeditembyscript) { |
|||
action: 'raw', |
|||
var cnum = (cin !== 'popup'), //if cin is a number, we're probably at initial load of one/many charts on a page, so we need to iterate over the entire queue |
|||
'ctype': 'application/json' |
|||
c = cnum ? _GEC.AIQueue.length : cin, //if not a number, its almost certainly 'popup', for which we only need to reload the popup |
|||
})).then(function (annodata) { |
|||
return annodata; |
|||
}, function () { |
|||
return []; |
|||
} //handles the page not existing |
|||
).then(function (annodata) { |
|||
_GEC.AIData.push({ |
|||
name: item, |
|||
data: Object.values(data)[0], |
|||
id: item, |
|||
gecchartid: gecchartid, |
|||
lineWidth: 2, |
|||
annotations: formatAnnoList(annodata, Object.values(data)[0]) |
|||
if (cnum) { //this structure repeats throughout the method: if cnum then loop else do once. probably a better way to do this |
|||
}); |
|||
_GEC.AILoaded.push(item); |
|||
if (getSeriesIndex(gecchartid, 'average') !== -1) { |
|||
isMedium[i] = $('#GEdatachart' + i).hasClass('mediumChart'); |
|||
_GEC['chart' + gecchartid].series[getSeriesIndex(gecchartid, 'average')].remove(); |
|||
} |
|||
} else { |
|||
isSmall = $('#GEdatachart' + c).hasClass('smallChart'); |
|||
isMedium = $('#GEdatachart' + c).hasClass('mediumChart'); |
|||
} |
} |
||
if (_GEC.AIQueue.length === _GEC.AILoaded.length) { |
|||
// This is always true when only 1 item is being loaded. |
|||
if (cnum) { |
|||
loadChartsQueueComplete(gecchartid); |
|||
if (getSeriesIndex(_GEC.AIQueue[i].chart, volumeLabel) !== -1) { |
|||
id = i === 'popup' ? $('#GEchartpopup').attr('data-chartid') : i; |
|||
getData(id, true, undefined, undefined, function(data) { |
|||
data[1].title.text = 'Price history'; |
|||
reloadChart(i, { |
|||
series: data[0], |
|||
yAxis: data[1] |
|||
}); |
|||
}); |
|||
} |
|||
} |
|||
} else { |
|||
if (getSeriesIndex(c, volumeLabel) !== -1) { |
|||
id = c === 'popup' ? $('#GEchartpopup').attr('data-chartid') : c; |
|||
getData(id, true, undefined, undefined, function(data) { |
|||
data[1].title.text = 'Price history'; |
|||
reloadChart(c, { |
|||
series: data[0], |
|||
yAxis: data[1] |
|||
}); |
|||
}); |
|||
} |
|||
} |
} |
||
}); |
|||
}); |
|||
_GEC.AIQueue.push({ |
|||
item: item.toLowerCase(), |
|||
chart: gecchartid |
|||
}); |
|||
// @todo when does this happen |
|||
for (i = 0; i < _GEC.AIData.length; i++) { |
|||
/* This happens when there are no further items added to the charts, i.e. when the original item is the only one. |
|||
index = -1; |
|||
This is indeed a flawed test, since it won't work on GEMW/C, where there is no original item in the chart. |
|||
for (var i2 = 0; i2 < _GEC.AIQueue.length; i2++) { |
|||
This should be replaced with another test that also works on GEMW/C. |
|||
if (_GEC.AIQueue[i2].item === (_GEC.AIData[i] || {name: ''}).name.toLowerCase()) { |
|||
index = i2; |
|||
break; |
|||
} |
|||
} |
|||
data[index !== -1 ? index : data.length] = _GEC.AIData[i]; |
|||
} |
|||
// @todo should this be `Array.isArray` |
|||
// or should it default to `{}` |
|||
// @todo test if isSmall is needed in the conditional |
|||
if (cnum) { |
|||
for (i = 0; i < c; i++) { |
|||
if (data[i] === undefined) continue; |
|||
if ((isSmall[data[i].gecchartid] && isMedium[data[i].gecchartid]) && typeof Array.isArray(_GEC.addedData[data[i].gecchartid])) { |
|||
_GEC.addedData[data[i].gecchartid] = []; |
|||
} |
|||
} |
|||
} else { |
|||
if ((isSmall || isMedium) && typeof Array.isArray(_GEC.addedData[data[c].gecchartid])) { |
|||
_GEC.addedData[data[c].gecchartid] = []; |
|||
} |
|||
} |
|||
for (i = 0; i < data.length; i++) { |
|||
if (data[i]) { |
|||
_GEC['chart' + data[i].gecchartid].addSeries(data[i]); |
|||
if (data[i].annotations && data[i].annotations.labels && data[i].annotations.labels.length) { |
|||
_GEC['chart' + data[i].gecchartid].addAnnotation(data[i].annotations); |
|||
} |
|||
} |
|||
if (cnum && isSmall[data[i].gecchartid]) { |
|||
_GEC.addedData[data[i].gecchartid][i] = data[i]; |
|||
} |
|||
} |
|||
if (cnum) { |
|||
for (i = 0; i < c; i++) { |
|||
setChartExtremes(data[i].gecchartid); |
|||
$('#extraItem' + data[i].gecchartid).data('ooui-elem').setDisabled(false).setValue(''); |
|||
} |
|||
} else { |
|||
setChartExtremes(c); |
|||
$('#extraItem' + c).data('ooui-elem').setDisabled(false).setValue(''); |
|||
} |
|||
itemhash = (location.hash.match(/#i=[^#]*/) || [])[0] || location.hash + '#i='; |
|||
$addedItems = $('#addedItems' + c); |
|||
for (i = 0; i < data.length; i++) { |
|||
if (!data[i]) { |
|||
continue; |
|||
} |
|||
iname = data[i].name; |
|||
if (!$addedItems.text().trim()) { |
|||
$addedItems.append( |
|||
'Remove items from graph: ', |
|||
$('<a>') |
|||
.attr({ |
|||
href: 'javascript:removeGraphItem("' + iname + '","' + c + '")', |
|||
'data-item': iname |
|||
}) |
|||
.text(iname) |
|||
); |
|||
itemhash = '#i=' + iname; |
|||
} else { |
|||
$addedItems.append( |
|||
', ', |
|||
$('<a>') |
|||
.attr({ |
|||
href: 'javascript:removeGraphItem("' + iname + '","' + c + '")', |
|||
'data-item': iname |
|||
}) |
|||
.text(iname) |
|||
); |
|||
itemhash += ',' + iname; |
|||
} |
|||
} |
|||
if (location.hash.match(/#i=/)) { |
|||
itemhash = location.hash |
|||
.replace(/#i=[^#]*/, itemhash) |
|||
.replace(/ /g, '_'); |
|||
} else { |
|||
itemhash = location.hash + itemhash; |
|||
} |
|||
if ( |
|||
( |
|||
conf.wgNamespaceNumber == 112 && conf.wgTitle.split('/')[1] == 'Data' || |
|||
conf.wgPageName == 'RuneScape:Grand_Exchange_Market_Watch/Chart' |
|||
) && |
|||
itemhash.replace('#i=', '').length |
|||
) { |
|||
location.hash = itemhash; |
|||
} |
|||
_GEC.AIQueue = []; |
|||
_GEC.AILoaded = []; |
|||
_GEC.AIData = []; |
|||
if (cnum) { |
|||
for (i = 0; i < c; i++) { |
|||
hadBlank = removeGraphItem('Blank', data[i].gecchartid); |
|||
if (hadBlank) { |
|||
setChartRange(data[i].gecchartid); |
|||
} |
|||
} |
|||
} else { |
|||
hadBlank = removeGraphItem('Blank', c); |
|||
if (hadBlank) { |
|||
setChartRange(c); |
|||
} |
|||
} |
|||
}, |
|||
/** |
|||
* <doc> |
|||
* |
|||
* @param c {number|string} |
|||
* |
|||
* @return {boolean} |
|||
*/ |
*/ |
||
} else if ($addedItems.html().match(/^\s*$/) || conf.wgPageName == 'RuneScape:Grand_Exchange_Market_Watch/Chart' && $addedItems.find('a').length === 1) { |
|||
setRange: function (c) { |
|||
id = i === 'popup' ? $('#GEchartpopup').attr('data-chartid') : i; |
|||
getData(id, false, false, i, function (data) { |
|||
zoom = zoom && zoom <= 6 && zoom >= 0 ? zoom - 1 : (zoom === 0 ? 0 : 2); |
|||
series = _GEC['chart' + i].series; |
|||
seriesIndex = getSeriesIndex(i, 'average'); |
|||
var hasT = (conf.wgNamespaceNumber === 112 && conf.wgTitle.split('/')[1] === 'Data') || conf.wgPageName === 'RuneScape:Grand_Exchange_Market_Watch/Chart'; |
|||
if (typeof c === 'number' && (hasT && !hash.match('#t=') || !hasT)) { |
|||
$('#GEdatachart' + c + ' .zoomButton').eq(zoom).click(); |
|||
return true; |
|||
} |
|||
//remove an average line if it already exists |
|||
var timespan = decodeURIComponent((hash.match(/#t=([^#]*)/) || [])[1] || '') |
|||
if (seriesIndex !== -1) { |
|||
series[seriesIndex].remove(); |
|||
} |
|||
var dates = [new Date(timespan[0]), new Date(timespan[1])]; |
|||
var d = new Date(timespan[0]); |
|||
var extremes = _GEC['chart' + c].xAxis[0].getExtremes(); |
|||
//add average line when there is only 1 item in the chart |
|||
if (dates[0] !== 'Invalid Date' && dates[1] === 'Invalid Date' && typeof zoom === 'number') { |
|||
_GEC['chart' + i].addSeries(data[0][1]); |
|||
}); |
|||
} |
|||
}, |
|||
/** |
|||
* <doc> |
|||
* |
|||
* @param c {number|string} |
|||
*/ |
|||
loadQueueComplete: function loadQueueComplete(cin, addeditembyscript) { |
|||
var cnum = cin !== 'popup', |
|||
//if cin is a number, we're probably at initial load of one/many charts on a page, so we need to iterate over the entire queue |
|||
c = cnum ? _GEC.AIQueue.length : cin, |
|||
//if not a number, its almost certainly 'popup', for which we only need to reload the popup |
|||
id, |
|||
chartdata, |
|||
isSmall = [], |
|||
isMedium = [], |
|||
data = [], |
|||
i, |
|||
index, |
|||
itemhash, |
|||
$addedItems, |
|||
iname, |
|||
hadBlank; |
|||
if (cnum) { |
|||
//this structure repeats throughout the method: if cnum then loop else do once. probably a better way to do this |
|||
for (i = 0; i < c; i++) { |
|||
isSmall[i] = $('#GEdatachart' + i).hasClass('smallChart'); |
|||
isMedium[i] = $('#GEdatachart' + i).hasClass('mediumChart'); |
|||
} |
|||
} else { |
|||
isSmall = $('#GEdatachart' + c).hasClass('smallChart'); |
|||
isMedium = $('#GEdatachart' + c).hasClass('mediumChart'); |
|||
} |
|||
if (cnum) { |
|||
for (i = 0; i < c; i++) { |
|||
if (getSeriesIndex(_GEC.AIQueue[i].chart, volumeLabel) !== -1) { |
|||
id = i === 'popup' ? $('#GEchartpopup').attr('data-chartid') : i; |
|||
getData(id, true, undefined, undefined, function (data) { |
|||
data[1].title.text = 'Price history'; |
|||
reloadChart(i, { |
|||
series: data[0], |
|||
yAxis: data[1] |
|||
}); |
|||
}); |
|||
} |
|||
} |
|||
} else { |
|||
if (getSeriesIndex(c, volumeLabel) !== -1) { |
|||
id = c === 'popup' ? $('#GEchartpopup').attr('data-chartid') : c; |
|||
getData(id, true, undefined, undefined, function (data) { |
|||
data[1].title.text = 'Price history'; |
|||
reloadChart(c, { |
|||
series: data[0], |
|||
yAxis: data[1] |
|||
}); |
|||
}); |
|||
} |
|||
} |
|||
for (i = 0; i < _GEC.AIData.length; i++) { |
|||
index = -1; |
|||
for (var i2 = 0; i2 < _GEC.AIQueue.length; i2++) { |
|||
if (_GEC.AIQueue[i2].item === (_GEC.AIData[i] || { |
|||
name: '' |
|||
}).name.toLowerCase()) { |
|||
index = i2; |
|||
break; |
|||
} |
|||
} |
|||
data[index !== -1 ? index : data.length] = _GEC.AIData[i]; |
|||
} |
|||
// @todo should this be `Array.isArray` |
|||
// or should it default to `{}` |
|||
// @todo test if isSmall is needed in the conditional |
|||
if (cnum) { |
|||
for (i = 0; i < c; i++) { |
|||
if (data[i] === undefined) continue; |
|||
if (isSmall[data[i].gecchartid] && isMedium[data[i].gecchartid] && _typeof(Array.isArray(_GEC.addedData[data[i].gecchartid]))) { |
|||
_GEC.addedData[data[i].gecchartid] = []; |
|||
} |
|||
} |
|||
} else { |
|||
if ((isSmall || isMedium) && _typeof(Array.isArray(_GEC.addedData[data[c].gecchartid]))) { |
|||
_GEC.addedData[data[c].gecchartid] = []; |
|||
} |
|||
} |
|||
for (i = 0; i < data.length; i++) { |
|||
if (data[i]) { |
|||
_GEC['chart' + data[i].gecchartid].addSeries(data[i]); |
|||
if (data[i].annotations && data[i].annotations.labels && data[i].annotations.labels.length) { |
|||
_GEC['chart' + data[i].gecchartid].addAnnotation(data[i].annotations); |
|||
} |
|||
} |
|||
if (cnum && isSmall[data[i].gecchartid]) { |
|||
_GEC.addedData[data[i].gecchartid][i] = data[i]; |
|||
} |
|||
} |
|||
if (cnum) { |
|||
for (i = 0; i < c; i++) { |
|||
setChartExtremes(data[i].gecchartid); |
|||
$('#extraItem' + data[i].gecchartid).data('ooui-elem').setDisabled(false).setValue(''); |
|||
} |
|||
} else { |
|||
setChartExtremes(c); |
|||
$('#extraItem' + c).data('ooui-elem').setDisabled(false).setValue(''); |
|||
} |
|||
itemhash = (location.hash.match(/#i=[^#]*/) || [])[0] || location.hash + '#i='; |
|||
$addedItems = $('#addedItems' + c); |
|||
for (i = 0; i < data.length; i++) { |
|||
if (!data[i]) { |
|||
continue; |
|||
} |
|||
iname = data[i].name; |
|||
if (!$addedItems.text().trim()) { |
|||
$addedItems.append('Remove items from graph: ', $('<a>').attr({ |
|||
href: 'javascript:removeGraphItem("' + iname + '","' + c + '")', |
|||
'data-item': iname |
|||
}).text(iname)); |
|||
itemhash = '#i=' + iname; |
|||
} else { |
|||
$addedItems.append(', ', $('<a>').attr({ |
|||
href: 'javascript:removeGraphItem("' + iname + '","' + c + '")', |
|||
'data-item': iname |
|||
}).text(iname)); |
|||
itemhash += ',' + iname; |
|||
} |
|||
} |
|||
if (location.hash.match(/#i=/)) { |
|||
itemhash = location.hash.replace(/#i=[^#]*/, itemhash).replace(/ /g, '_'); |
|||
} else { |
|||
itemhash = location.hash + itemhash; |
|||
} |
|||
if ((conf.wgNamespaceNumber == 112 && conf.wgTitle.split('/')[1] == 'Data' || conf.wgPageName == 'RuneScape:Grand_Exchange_Market_Watch/Chart') && itemhash.replace('#i=', '').length) { |
|||
location.hash = itemhash; |
|||
} |
|||
_GEC.AIQueue = []; |
|||
_GEC.AILoaded = []; |
|||
_GEC.AIData = []; |
|||
if (cnum) { |
|||
for (i = 0; i < c; i++) { |
|||
hadBlank = removeGraphItem('Blank', data[i].gecchartid); |
|||
if (hadBlank) { |
|||
setChartRange(data[i].gecchartid); |
|||
} |
|||
} |
|||
} else { |
|||
hadBlank = removeGraphItem('Blank', c); |
|||
if (hadBlank) { |
|||
setChartRange(c); |
|||
} |
|||
} |
|||
}, |
|||
/** |
|||
* <doc> |
|||
* |
|||
* @param c {number|string} |
|||
* |
|||
* @return {boolean} |
|||
*/ |
|||
setRange: function setRange(c) { |
|||
var zoom = parseInt((location.hash.match(/#z=([^#]*)/) || [])[1], 10); |
|||
zoom = zoom && zoom <= 6 && zoom >= 0 ? zoom - 1 : zoom === 0 ? 0 : 2; |
|||
var hash = location.hash; |
|||
var hasT = conf.wgNamespaceNumber === 112 && conf.wgTitle.split('/')[1] === 'Data' || conf.wgPageName === 'RuneScape:Grand_Exchange_Market_Watch/Chart'; |
|||
if (typeof c === 'number' && (hasT && !hash.match('#t=') || !hasT)) { |
|||
$('#GEdatachart' + c + ' .zoomButton').eq(zoom).click(); |
|||
return true; |
|||
} |
|||
var timespan = decodeURIComponent((hash.match(/#t=([^#]*)/) || [])[1] || '').replace(/_/g, ' ').split(','); |
|||
var dates = [new Date(timespan[0]), new Date(timespan[1])]; |
|||
var d = new Date(timespan[0]); |
|||
var extremes = _GEC['chart' + c].xAxis[0].getExtremes(); |
|||
if (dates[0] !== 'Invalid Date' && dates[1] === 'Invalid Date' && typeof zoom === 'number') { |
|||
var button = _GEC['chart' + c].rangeSelector.buttonOptions[zoom]; |
|||
if (button.type === 'month') { |
|||
d.setMonth(d.getMonth() + button.count); |
|||
} else if (button.type === 'year') { |
|||
d.setYear(d.getFullYear() + button.count); |
|||
} else if (button.type === 'all') { |
|||
d = new Date(extremes.dataMax); |
|||
} |
|||
dates[1] = d; |
|||
} |
|||
if (dates[0] !== 'Invalid Date' && dates[1] !== 'Invalid Date') { |
|||
_GEC['chart' + c].xAxis[0].setExtremes(dates[0].getTime(), dates[1].getTime()); |
|||
return true; |
|||
} |
|||
return false; |
|||
}, |
|||
/** |
|||
* <doc> |
|||
* |
|||
* @param c {number|string} |
|||
* @param change {object} |
|||
*/ |
|||
reload: function reload(c, change) { |
|||
var options = _GEC['chart' + c].options; |
|||
if (!options) { |
|||
// @todo do we need to return `false` here |
|||
// @todo when does this happen |
|||
return false; |
|||
} |
|||
$.extend(options, change); |
|||
_GEC['chart' + c] = new Highcharts.StockChart(options); |
|||
}, |
|||
/** |
|||
* <doc> |
|||
* |
|||
* @param item {string} |
|||
* @param c {number|string} |
|||
* |
|||
* @return {boolean} |
|||
*/ |
|||
removeItem: function removeItem(item, c) { |
|||
var series = _GEC['chart' + c].series, |
|||
id, |
|||
i, |
|||
newhash, |
|||
data; |
|||
// find the item we want to remove |
|||
if (button.type === 'month') { |
|||
for (i = 0; i < series.length; i++) { |
|||
if (series[i].name.match(item)) { |
|||
id = i; |
|||
d.setYear(d.getFullYear() + button.count); |
|||
} |
|||
} else if (button.type === 'all') { |
|||
} |
|||
d = new Date(extremes.dataMax); |
|||
} |
|||
// @todo when does this happen |
|||
// when we can't find the item? |
|||
if (typeof id !== 'number') { |
|||
return false; |
|||
} |
|||
// remove item from url hash |
|||
if (dates[0] !== 'Invalid Date' && dates[1] !== 'Invalid Date') { |
|||
newhash = location.hash.replace(/_/g, ' ').replace(new RegExp('(#i=[^#]*),?' + item, 'i'), '$1').replace(/,,/g, ',').replace(/,#/g, '#').replace(/#i=,/g, '#i=').replace(/#i=($|#)/, '$1').replace(/ /g, '_'); |
|||
_GEC['chart' + c].xAxis[0].setExtremes(dates[0].getTime(), dates[1].getTime()); |
|||
if (newhash.replace('#i=', '').length) { |
|||
return true; |
|||
location.hash = newhash; |
|||
} else if (location.hash.length) { |
|||
location.hash = ''; |
|||
} |
|||
// remove the item from the chart |
|||
series[id].remove(); |
|||
// reset extremes? |
|||
setChartExtremes(c); |
|||
// @todo can we cache #addedItems somehow |
|||
/** |
|||
// remove item from list at top of graph |
|||
$('#addedItems' + c + ' [data-item="' + item + '"]').remove(); |
|||
* |
|||
// cleanup list |
|||
* @param c {number|string} |
|||
$('#addedItems' + c).html($('#addedItems' + c).html().replace(/, , /g, ', ').replace(/, $/, '').replace(': , ', ': ')); |
|||
* @param change {object} |
|||
*/ |
|||
reload: function (c, change) { |
|||
var options = _GEC['chart' + c].options; |
|||
// if the list is empty show average, volume and item stats again |
|||
if (!options) { |
|||
if (!$('#addedItems' + c + ' [data-item]').length) { |
|||
// @todo do we need to return `false` here |
|||
$('#addedItems' + c).empty(); |
|||
// @todo when does this happen |
|||
id = c == 'popup' ? $('#GEchartpopup').attr('data-chartid') : c; |
|||
data = getData(id, false, false, 'popup', function (data) { |
|||
} |
|||
reloadChart(c, { |
|||
series: data[0], |
|||
yAxis: data[1] |
|||
_GEC['chart' + c] = new Highcharts.StockChart(options); |
|||
} |
}); |
||
}); |
|||
} |
|||
return true; |
|||
}, |
|||
/** |
|||
* @param item {string} |
|||
* <doc> |
|||
* @param c {number|string} |
|||
* |
|||
* @param i {number|string} |
|||
*/ |
|||
popup: function popup() {}, |
|||
/** |
|||
var series = _GEC['chart' + c].series, |
|||
* <doc> |
|||
* |
|||
* @param i |
|||
*/ |
|||
setExtremes: function setExtremes(i) { |
|||
var ch = _GEC['chart' + i], |
|||
// find the item we want to remove |
|||
exts = _GEC['chart' + i].yAxis[0].getExtremes(); |
|||
if (exts.dataMin * 0.95 !== exts.userMin || exts.dataMax * 1.05 !== exts.userMax) { |
|||
ch.yAxis[0].setExtremes(exts.dataMin * 0.95, exts.dataMax * 1.05); |
|||
id = i; |
|||
if (ch.yAxis[2]) { |
|||
exts = ch.yAxis[1].getExtremes(); |
|||
ch.yAxis[1].setExtremes(0, exts.dataMax * 1.05); |
|||
// @todo when does this happen |
|||
// when we can't find the item? |
|||
if (typeof id !== 'number') { |
|||
return false; |
|||
} |
|||
// remove item from url hash |
|||
newhash = location.hash |
|||
.replace(/_/g, ' ') |
|||
.replace(new RegExp('(#i=[^#]*),?' + item, 'i'), '$1') |
|||
.replace(/,,/g, ',') |
|||
.replace(/,#/g, '#') |
|||
.replace(/#i=,/g, '#i=') |
|||
.replace(/#i=($|#)/, '$1') |
|||
.replace(/ /g, '_'); |
|||
if (newhash.replace('#i=', '').length) { |
|||
location.hash = newhash; |
|||
} else if (location.hash.length) { |
|||
location.hash = ''; |
|||
} |
|||
// remove the item from the chart |
|||
series[id].remove(); |
|||
// reset extremes? |
|||
setChartExtremes(c); |
|||
// @todo can we cache #addedItems somehow |
|||
// remove item from list at top of graph |
|||
$('#addedItems' + c + ' [data-item="' + item + '"]').remove(); |
|||
// cleanup list |
|||
$('#addedItems' + c).html( |
|||
$('#addedItems' + c) |
|||
.html() |
|||
.replace(/, , /g, ', ') |
|||
.replace(/, $/, '') |
|||
.replace(': , ', ': ') |
|||
); |
|||
// if the list is empty show average, volume and item stats again |
|||
if (!$('#addedItems' + c + ' [data-item]').length) { |
|||
$('#addedItems' + c).empty(); |
|||
id = c == 'popup' ? $('#GEchartpopup').attr('data-chartid') : c; |
|||
data = getData(id, false, false, 'popup', function(data) { |
|||
reloadChart(c, { |
|||
series: data[0], |
|||
yAxis: data[1] |
|||
}); |
|||
}); |
|||
} |
|||
return true; |
|||
}, |
|||
/** |
|||
* <doc> |
|||
* |
|||
* @param i {number|string} |
|||
*/ |
|||
popup: function () { |
|||
}, |
|||
/** |
|||
* <doc> |
|||
* |
|||
* @param i |
|||
*/ |
|||
setExtremes: function (i) { |
|||
var ch = _GEC['chart' + i], |
|||
exts = _GEC['chart' + i].yAxis[0].getExtremes(); |
|||
if ( |
|||
exts.dataMin * 0.95 !== exts.userMin || |
|||
exts.dataMax * 1.05 !== exts.userMax |
|||
) { |
|||
ch.yAxis[0].setExtremes(exts.dataMin * 0.95, exts.dataMax * 1.05); |
|||
if (ch.yAxis[2]) { |
|||
exts = ch.yAxis[1].getExtremes(); |
|||
ch.yAxis[1].setExtremes(0, exts.dataMax * 1.05); |
|||
} |
|||
} |
|||
if (i === 'popup') { |
|||
// @todo use onclick event |
|||
$('#GEPermLink' + i).data('ooui-elem').setData(chartPermLinkUrl(i)); |
|||
} |
|||
}, |
|||
/** |
|||
* <doc> |
|||
* |
|||
* @param c {number|string} |
|||
* @param isSmall {boolean} |
|||
* @param avginput {number|string} (optional) |
|||
* number component of input element used for altering the average interval |
|||
* when the interval is in days |
|||
* when is this different to `c`? |
|||
* |
|||
* @return {array} 2 item array containing X and Y respectively |
|||
* @todo expand on what X and Y are |
|||
*/ |
|||
getData: function () { |
|||
} |
} |
||
} |
|||
if (i === 'popup') { |
|||
// @todo use onclick event |
|||
$('#GEPermLink' + i).data('ooui-elem').setData(chartPermLinkUrl(i)); |
|||
} |
|||
}, |
}, |
||
/** |
|||
* <doc> |
|||
// map old functions to new locations until uses are fixed |
|||
* |
|||
getSeriesIndex = chart.getSeriesIndex, |
|||
* @param c {number|string} |
|||
chartPermLinkUrl = chart.permLinkUrl, |
|||
* @param isSmall {boolean} |
|||
addItem = chart.addItem, |
|||
* @param avginput {number|string} (optional) |
|||
removeGraphItem = chart.removeItem, |
|||
* number component of input element used for altering the average interval |
|||
reloadChart = chart.reload, |
|||
* when the interval is in days |
|||
setChartRange = chart.setRange, |
|||
* when is this different to `c`? |
|||
setChartExtremes = chart.setExtremes, |
|||
* |
|||
loadChartsQueueComplete = chart.loadQueueComplete; |
|||
* @return {array} 2 item array containing X and Y respectively |
|||
* @todo expand on what X and Y are |
|||
*/ |
|||
getData: function getData() {} |
|||
}, |
|||
// map old functions to new locations until uses are fixed |
|||
getSeriesIndex = chart.getSeriesIndex, |
|||
chartPermLinkUrl = chart.permLinkUrl, |
|||
addItem = chart.addItem, |
|||
removeGraphItem = chart.removeItem, |
|||
reloadChart = chart.reload, |
|||
setChartRange = chart.setRange, |
|||
setChartExtremes = chart.setExtremes, |
|||
loadChartsQueueComplete = chart.loadQueueComplete; |
|||
// popupChart = chart.popup; |
// popupChart = chart.popup; |
||
// getData = chart.getData; |
// getData = chart.getData; |
||
Line 1,278: | Line 1,077: | ||
function popupChart(i) { |
function popupChart(i) { |
||
var $popup = $('#GEchartpopup'), |
|||
$overlay = $('#overlay'), |
|||
options, |
|||
data, |
|||
n; |
|||
if (!$popup.length) { |
|||
return false; |
|||
if (!$popup.length) { |
|||
} |
|||
return false; |
|||
if ($overlay.length) { |
|||
} |
|||
$overlay.toggle(); |
|||
} else { |
|||
if ($overlay.length) { |
|||
$popup.before($('<div>').attr('id', 'overlay').css('display', 'block')); |
|||
$overlay.toggle(); |
|||
$overlay = $('#overlay'); |
|||
} else { |
|||
} |
|||
$popup.before( |
|||
$overlay.on('click', function () { |
|||
$('<div>') |
|||
popupChart(false); |
|||
.attr('id', 'overlay') |
|||
}); |
|||
.css('display', 'block') |
|||
if (typeof i === 'number') { |
|||
$(document).keydown(function (e) { |
|||
$overlay = $('#overlay'); |
|||
// Esc |
|||
if (e.which === 27) { |
|||
$overlay.on('click', function () { |
|||
popupChart(false); |
popupChart(false); |
||
} |
|||
}); |
|||
} else { |
|||
// @todo only remove our event |
|||
$(document).off('keydown'); |
|||
} |
|||
if (typeof i === 'boolean' && !i) { |
|||
$popup.hide(); |
|||
$('#addedItemspopup').html(''); |
|||
} else { |
|||
$popup.toggle(); |
|||
} |
|||
if (typeof i === 'number' && $popup.attr('data-chartid') !== i) { |
|||
$('#averagepopup').data('ooui-elem').setValue(_GEC.average); |
|||
$popup.attr('data-chartid', i); |
|||
options = {}; |
|||
getData(i, false, false, 'popup', function (data) { |
|||
var dataList = data[0]; |
|||
var yAxis = data[1]; |
|||
// @todo can this be replaced with $.extend? |
|||
// @todo what is this supposed to do? |
|||
util.cloneObj(_GEC['chart' + i].options, options); |
|||
options.chart.renderTo = 'GEpopupchart'; |
|||
options.legend.enabled = true; |
|||
options.title.text = 'Grand Exchange Market Watch'; |
|||
options.title.style.fontSize = '18px'; |
|||
options.subtitle.text = options.series[0].name; |
|||
options.subtitle.y = 35; |
|||
options.subtitle.style.fontSize = '15px;'; |
|||
options.chart.zoomType = ''; |
|||
options.rangeSelector.enabled = true; |
|||
options.rangeSelector.inputBoxStyle.display = 'block'; |
|||
options.plotOptions.series.enableMouseTracking = true; |
|||
options.tooltip.enabled = true; |
|||
options.navigator.enabled = true; |
|||
options.credits.enabled = false; |
|||
options.series = [{}]; |
|||
options.series = _GEC.addedData[i] ? [dataList[0]] : dataList; |
|||
options.yAxis = yAxis; |
|||
options.annotations = [data[2]]; |
|||
_GEC.chartpopup = new Highcharts.StockChart(options); |
|||
if (_GEC.addedData[i]) { |
|||
for (n = 0; n < _GEC.addedData[i].length; n++) { |
|||
_GEC.chartpopup.addSeries(_GEC.addedData[i][n]); |
|||
} |
|||
} |
|||
setChartExtremes('popup'); |
|||
_GEC.chartpopup.redraw(); |
|||
$('#GEPermLinkpopup').data('ooui-elem').setData(chartPermLinkUrl('popup')); |
|||
}); |
}); |
||
} |
|||
if (typeof i === 'number') { |
|||
$(document).keydown(function (e) { |
|||
// Esc |
|||
if (e.which === 27) { |
|||
popupChart(false); |
|||
} |
|||
}); |
|||
} else { |
|||
// @todo only remove our event |
|||
$(document).off('keydown'); |
|||
} |
|||
if (typeof i === 'boolean' && !i) { |
|||
$popup.hide(); |
|||
$('#addedItemspopup').html(''); |
|||
} else { |
|||
$popup.toggle(); |
|||
} |
|||
if (typeof i === 'number' && $popup.attr('data-chartid') !== i) { |
|||
$('#averagepopup').data('ooui-elem').setValue(_GEC.average); |
|||
$popup.attr('data-chartid', i); |
|||
options = {}; |
|||
getData(i, false, false, 'popup', function(data) { |
|||
var dataList = data[0]; |
|||
var yAxis = data[1]; |
|||
// @todo can this be replaced with $.extend? |
|||
// @todo what is this supposed to do? |
|||
util.cloneObj(_GEC['chart' + i].options, options); |
|||
options.chart.renderTo = 'GEpopupchart'; |
|||
options.legend.enabled = true; |
|||
options.title.text = 'Grand Exchange Market Watch'; |
|||
options.title.style.fontSize = '18px'; |
|||
options.subtitle.text = options.series[0].name; |
|||
options.subtitle.y = 35; |
|||
options.subtitle.style.fontSize = '15px;'; |
|||
options.chart.zoomType = ''; |
|||
options.rangeSelector.enabled = true; |
|||
options.rangeSelector.inputBoxStyle.display = 'block'; |
|||
options.plotOptions.series.enableMouseTracking = true; |
|||
options.tooltip.enabled = true; |
|||
options.navigator.enabled = true; |
|||
options.credits.enabled = false; |
|||
options.series = [{}]; |
|||
options.series = _GEC.addedData[i] ? [dataList[0]] : dataList; |
|||
options.yAxis = yAxis; |
|||
options.annotations = [data[2]]; |
|||
_GEC.chartpopup = new Highcharts.StockChart(options); |
|||
if (_GEC.addedData[i]) { |
|||
for (n = 0; n < _GEC.addedData[i].length; n++) { |
|||
_GEC.chartpopup.addSeries(_GEC.addedData[i][n]); |
|||
} |
|||
} |
|||
setChartExtremes('popup'); |
|||
_GEC.chartpopup.redraw(); |
|||
$('#GEPermLinkpopup').data('ooui-elem').setData(chartPermLinkUrl('popup')); |
|||
}); |
|||
} |
|||
} |
} |
||
function rg(num) { |
function rg(num) { |
||
var colour = 'red'; |
|||
if (num > 0) { |
|||
colour = 'green'; |
|||
} else if (num === 0) { |
|||
colour = 'blue'; |
|||
} |
|||
colour = 'blue'; |
|||
return colour; |
|||
} |
|||
return colour; |
|||
} |
} |
||
function formatAnnoList(annoData, prices) { |
function formatAnnoList(annoData, prices) { |
||
var annoList = { |
|||
labelOptions: { |
|||
verticalAlign: 'bottom', |
|||
overflow: 'justify', |
|||
y: -5 |
|||
}, |
|||
draggable: '', |
|||
visible: window._GEC.annotationsVisible, |
|||
labels: [] |
|||
}; |
|||
for (var i = 0; i < annoData.length; i++) { |
|||
var ad = annoData[i]; |
|||
var label = ad.title; |
|||
if (!(ad.link === undefined || ad.link === null || ad.link === '')) { |
|||
var labelUrl; |
|||
if (ad.link.startsWith('https://') || ad.link.startsWith('http://')) { |
|||
labelUrl = ad.link; |
|||
} else { |
|||
labelUrl = mw.util.getUrl(ad.link); |
|||
} |
|||
label = '<a href="' + labelUrl + '" target="_blank">' + ad.title + '</a>'; |
|||
} |
} |
||
var ts = Date.parse(ad.timestamp); |
|||
if (!isNaN(ts)) { |
|||
var ad = annoData[i]; |
|||
var priceAtTs = 0; |
|||
for (var i2 = 0; i2 < prices.length; i2++) { |
|||
if (prices[i2][0] === ts) { |
|||
priceAtTs = prices[i2][1]; |
|||
if (ad.link.startsWith('https://') || ad.link.startsWith('http://')) { |
|||
break; |
|||
} else { |
|||
labelUrl = mw.util.getUrl(ad.link); |
|||
} |
|||
label = '<a href="'+labelUrl+'" target="_blank">'+ad.title+'</a>'; |
|||
} |
} |
||
if (prices[i2][0] > ts) { |
|||
if ( |
if (i2 > 0) { |
||
if (prices[i2 - 1][0] < ts) { |
|||
priceAtTs = prices[i2][1]; |
|||
break; |
|||
priceAtTs = prices[i2][1]; |
|||
break; |
|||
} |
|||
if (prices[i2][0] > ts) { |
|||
if (i2 > 0) { |
|||
if (prices[i2-1][0] < ts) { |
|||
priceAtTs = prices[i2][1]; |
|||
break; |
|||
} |
|||
} else { |
|||
priceAtTs = prices[i2][1]; |
|||
break; |
|||
} |
|||
} |
|||
} |
} |
||
} else { |
|||
priceAtTs = prices[i2][1]; |
|||
break; |
|||
} |
|||
} |
|||
} |
|||
var anno = { |
|||
text: label, |
|||
point: { |
|||
xAxis: 0, |
|||
yAxis: 0, |
|||
x: ts, |
|||
y: priceAtTs |
|||
} |
} |
||
}; |
|||
annoList.labels.push(anno); |
|||
} |
} |
||
} |
|||
return annoList; |
|||
return annoList; |
|||
} |
} |
||
function getData(cin, isSmall, isMedium, avginput, callback) { |
function getData(cin, isSmall, isMedium, avginput, callback) { |
||
var c = cin === 'popup' ? $('#GEchartpopup').attr('data-chartid') : cin, |
|||
$dataPrices = $('#GEdataprices' + c), |
|||
dataItem = $dataPrices.attr('data-item'), |
|||
dataItemId = $dataPrices.attr('data-itemId') || 'GE ' + dataItem, |
|||
isIndexChart = /index/i.test(dataItem), |
|||
itemName = dataItem || conf.wgTitle.split('/')[0], |
|||
ch = _GEC['chart' + c], |
|||
chartLoaded = !!(ch && ch.series && ch.series.length), |
|||
prices = [], |
|||
i, |
|||
data = [], |
|||
thisprice, |
|||
volumes = [], |
|||
dataList, |
|||
inputAvg, |
|||
newhash, |
|||
yAxis, |
|||
chartPageData; |
|||
// happens when the first chart isSmall |
|||
// and the average input id is actually the popup chart |
|||
// the chart's id is popup, but the input's id is 0 |
|||
avginput = avginput || cin; |
|||
var pricesToDataList = function pricesToDataList(inputData) { |
|||
var prices = inputData[0], |
|||
annoData = inputData[1]; |
|||
_GEC.urlCache[url] = prices; |
|||
_GEC.urlCache[annoUrl] = annoData; |
|||
prices = Object.values(prices)[0]; |
|||
var volumeMultiplier = 1; //isOSRS ? 1 : 1000000 |
|||
for (i = 0; i < prices.length; i++) { |
|||
data.push([ |
|||
// time |
|||
prices[i][0], |
|||
// @todo should this be parseInt? |
|||
// price |
|||
prices[i][1]]); |
|||
if (prices[i][2] && !isSmall) { |
|||
volumes.push([ |
|||
// time |
|||
prices[i][0], |
|||
// volume |
|||
// volumes are in millions |
|||
prices[i][2] * volumeMultiplier]); |
|||
} |
|||
} |
|||
// datalist's elements are essentially each line on the chart |
|||
var pricesToDataList = function(inputData) { |
|||
// so price, 30-day-average and volume |
|||
var prices = inputData[0], annoData = inputData[1]; |
|||
dataList = [{ |
|||
_GEC.urlCache[url] = prices; |
|||
name: itemName, |
|||
_GEC.urlCache[annoUrl] = annoData; |
|||
data: data, |
|||
prices = Object.values(prices)[0]; |
|||
lineWidth: isSmall ? 2 : 3 |
|||
var volumeMultiplier = 1;//isOSRS ? 1 : 1000000 |
|||
}]; |
|||
for (i = 0; i < prices.length; i++) { |
|||
if (itemName.toLowerCase() === 'blank' && !chartLoaded) { |
|||
data.push([ |
|||
dataList[0].color = '#000000'; |
|||
// time |
|||
} |
|||
prices[i][0], |
|||
if (!isSmall && !isMedium && (itemName.toLowerCase() !== 'blank' || chartLoaded)) { |
|||
// @todo should this be parseInt? |
|||
inputAvg = $('#average' + avginput).data('ooui-elem').getNumericValue(); |
|||
// price |
|||
prices[i][1] |
|||
]); |
|||
// @todo should this be isNaN? |
|||
if (prices[i][2] && !isSmall) { |
|||
if (inputAvg) { |
|||
volumes.push([ |
|||
newhash = location.hash.replace(/#a=[^#]*|$/, '#a=' + inputAvg).replace(/ /g, '_'); |
|||
// time |
|||
if (newhash.length) { |
|||
prices[i][0], |
|||
location.hash = newhash; |
|||
// volumes are in millions |
|||
prices[i][2] * volumeMultiplier |
|||
]); |
|||
} |
|||
} |
} |
||
} |
|||
inputAvg = inputAvg || 30; |
|||
dataList.push({ |
|||
name: inputAvg + '-day average', |
|||
data: util.avg(data, inputAvg, isIndexChart ? 2 : 0), |
|||
lineWidth: 2, |
|||
dashStyle: 'shortdash' |
|||
}); |
|||
if (volumes.length >= 10) { |
|||
dataList.push({ |
|||
name: volumeLabel, |
|||
data: volumes, |
|||
type: 'area', |
|||
color: '#cc8400', |
|||
fillColor: { |
|||
linearGradient: { |
|||
x1: 0, |
|||
y1: 0, |
|||
x2: 0, |
|||
y2: 1 |
|||
}, |
|||
stops: [[0, '#ffa500'], [1, 'white']] |
|||
}, |
|||
// display on separate y-axis |
|||
yAxis: 1 |
|||
}); |
|||
} |
|||
} |
|||
// create y-axis for price data |
|||
// datalist's elements are essentially each line on the chart |
|||
yAxis = { |
|||
// so price, 30-day-average and volume |
|||
title: { |
|||
text: isSmall ? null : isIndexChart ? 'Index history' : 'Price history', |
|||
name: itemName, |
|||
offset: 60, |
|||
rotation: 270, |
|||
style: { |
|||
color: 'black', |
|||
fontSize: '12px' |
|||
if (itemName.toLowerCase() === 'blank' && !chartLoaded) { |
|||
dataList[0].color = '#000000'; |
|||
} |
} |
||
}, |
|||
opposite: false, |
|||
labels: { |
|||
align: 'right', |
|||
x: -8, |
|||
y: 4 |
|||
}, |
|||
allowDecimals: false, |
|||
// 1 coin |
|||
minTickInterval: 1, |
|||
showLastLabel: 1, |
|||
lineWidth: 1, |
|||
lineColor: '#E0E0E0' |
|||
}; |
|||
// volume data is plotted on a seperate y-axis |
|||
if (!isSmall && !isMedium && (itemName.toLowerCase() !== 'blank' || chartLoaded)) { |
|||
if (volumes.length >= 10 && !isSmall) { |
|||
inputAvg = $('#average' + avginput).data('ooui-elem').getNumericValue(); |
|||
// set height to allow room for second y-axis |
|||
yAxis.height = 200; |
|||
// convert to array and add volume data |
|||
yAxis = [yAxis, { |
|||
title: { |
|||
newhash = location.hash |
|||
text: volumeLabel, |
|||
.replace(/#a=[^#]*|$/, '#a=' + inputAvg) |
|||
offset: 60, |
|||
rotation: 270, |
|||
style: { |
|||
color: 'black', |
|||
fontSize: '12px' |
|||
} |
|||
}, |
|||
opposite: false, |
|||
inputAvg = inputAvg || 30; |
|||
labels: { |
|||
align: 'right', |
|||
x: -8, |
|||
data: util.avg(data, inputAvg, isIndexChart ? 2 : 0), |
|||
y: 4 |
|||
}, |
|||
showEmpty: 0, |
|||
showLastLabel: 1, |
|||
offset: 0, |
|||
if (volumes.length >= 10) { |
|||
lineWidth: 1, |
|||
lineColor: '#E0E0E0', |
|||
height: 50, |
|||
top: 325, |
|||
min: 0 |
|||
}]; |
|||
fillColor: { |
|||
linearGradient: { |
|||
x1: 0, |
|||
y1: 0, |
|||
x2: 0, |
|||
y2: 1 |
|||
}, |
|||
stops: [ |
|||
[0, '#ffa500'], |
|||
[1, 'white'] |
|||
], |
|||
}, |
|||
// display on separate y-axis |
|||
yAxis: 1, |
|||
}); |
|||
} |
|||
} |
|||
// create y-axis for price data |
|||
yAxis = { |
|||
title: { |
|||
text: isSmall ? null : (isIndexChart ? 'Index history' : 'Price history'), |
|||
offset: 60, |
|||
rotation: 270, |
|||
style: { |
|||
color: 'black', |
|||
fontSize: '12px', |
|||
}, |
|||
}, |
|||
opposite: false, |
|||
labels: { |
|||
align: 'right', |
|||
x: -8, |
|||
y: 4, |
|||
}, |
|||
allowDecimals: false, |
|||
// 1 coin |
|||
minTickInterval: 1, |
|||
showLastLabel: 1, |
|||
lineWidth: 1, |
|||
lineColor: '#E0E0E0' |
|||
}; |
|||
// volume data is plotted on a seperate y-axis |
|||
if (volumes.length >= 10 && !isSmall) { |
|||
// set height to allow room for second y-axis |
|||
yAxis.height = 200; |
|||
// convert to array and add volume data |
|||
yAxis = [yAxis, { |
|||
title: { |
|||
text: volumeLabel, |
|||
offset: 60, |
|||
rotation: 270, |
|||
style: { |
|||
color: 'black', |
|||
fontSize: '12px' |
|||
} |
|||
}, |
|||
opposite: false, |
|||
labels: { |
|||
align: 'right', |
|||
x: -8, |
|||
y: 4, |
|||
}, |
|||
showEmpty: 0, |
|||
showLastLabel: 1, |
|||
offset: 0, |
|||
lineWidth: 1, |
|||
lineColor: '#E0E0E0', |
|||
height: 50, |
|||
top: 325, |
|||
min: 0 |
|||
}]; |
|||
} |
|||
var annoList = {}; |
|||
if (!isSmall) { |
|||
annoList = formatAnnoList(annoData, prices); |
|||
} |
|||
return [dataList, yAxis, annoList]; |
|||
} |
} |
||
var annoList = {}; |
|||
if (!isSmall) { |
|||
annoList = formatAnnoList(annoData, prices); |
|||
var dataType = isPopup ? 'all' : 'sample'; |
|||
var url = "https://api.weirdgloop.org/exchange/history/" + gameVersion + "/" + dataType + "?compress=true&id=" + dataItemId; |
|||
var annoUrl = mw.util.getUrl('Module:Exchange/'+itemName+'/annotations.json', {action:'raw', 'ctype':'application/json'}); |
|||
var pricesPromise; |
|||
if (chartLoaded && itemName.toLowerCase() === 'blank') { |
|||
chartPageData = _GEC['chart' + c].series[ |
|||
getSeriesIndex(c, $('#addedItems' + c).find('a').data('item')) |
|||
]; |
|||
for (i = 0; i < chartPageData.xData.length; i++) { |
|||
prices.push(chartPageData.xData[i] + ':' + chartPageData.yData[i]); |
|||
} |
|||
pricesPromise = Promise.resolve(prices); |
|||
} else { |
|||
if (_GEC.urlCache[url]) { |
|||
return callback(pricesToDataList([_GEC.urlCache[url], _GEC.urlCache[annoUrl]])) |
|||
} |
|||
Promise.all([ |
|||
// combine these two calls |
|||
// this fetches the price data from the API |
|||
// and the annotation data from the wiki |
|||
// the wiki annotation page may not exist, which returns the second part of the then |
|||
$.getJSON(url), |
|||
$.getJSON(annoUrl).then(function(d){return d}, function(){return []}) //on exception, just use an empty list |
|||
]).then(pricesToDataList).then(callback) |
|||
} |
} |
||
return [dataList, yAxis, annoList]; |
|||
}; |
|||
var isPopup = !isSmall && !isMedium; |
|||
var dataType = isPopup ? 'all' : 'sample'; |
|||
var url = "https://api.weirdgloop.org/exchange/history/" + gameVersion + "/" + dataType + "?compress=true&id=" + dataItemId; |
|||
var annoUrl = mw.util.getUrl('Module:Exchange/' + itemName + '/annotations.json', { |
|||
action: 'raw', |
|||
'ctype': 'application/json' |
|||
}); |
|||
var pricesPromise; |
|||
if (chartLoaded && itemName.toLowerCase() === 'blank') { |
|||
chartPageData = _GEC['chart' + c].series[getSeriesIndex(c, $('#addedItems' + c).find('a').data('item'))]; |
|||
for (i = 0; i < chartPageData.xData.length; i++) { |
|||
prices.push(chartPageData.xData[i] + ':' + chartPageData.yData[i]); |
|||
} |
|||
pricesPromise = Promise.resolve(prices); |
|||
} else { |
|||
if (_GEC.urlCache[url]) { |
|||
return callback(pricesToDataList([_GEC.urlCache[url], _GEC.urlCache[annoUrl]])); |
|||
} |
|||
Promise.all([ |
|||
// combine these two calls |
|||
// this fetches the price data from the API |
|||
// and the annotation data from the wiki |
|||
// the wiki annotation page may not exist, which returns the second part of the then |
|||
$.getJSON(url), $.getJSON(annoUrl).then(function (d) { |
|||
return d; |
|||
}, function () { |
|||
return []; |
|||
}) //on exception, just use an empty list |
|||
]).then(pricesToDataList).then(callback); |
|||
} |
|||
} |
} |
||
$(self.deps); |
$(self.deps); |
Latest revision as of 12:06, 20 October 2024
/**
* Grand Exchange Charts
* Displays price data of item(s) in a chart
*
* Highstock docs <https://api.highcharts.com/highstock/>
* Highstock change log <https://www.highcharts.com/blog/changelog/#highstock>
*
* @author Joeytje50
* @author Cqm
* @author JaydenKieran
* @author Gaz Lloyd
*
* @todo move Highcharts to a core ResourceLoader module
*
* @todo use a consistent variable for the chart id
* currently it's one of c, i or id
* @todo remove script URLs (javascript:func) in favour of onclick events
* may require attaching the events after the some parts have loaded
* @todo fix averages
*/
/*global jQuery, mediaWiki, rswiki, Highcharts, wgPageName, wgTitle, wgNamespaceNumber */
'use strict';
/**
* Cache mw.config variables
*/
function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
var conf = mw.config.get(['wgNamespaceNumber', 'wgPageName', 'wgTitle', 'wgSiteName']),
// Are we on OSRS? Impacts selectors and volume labels / multipliers
isOSRS = conf.wgSiteName == "RuneRealm Wiki",
// Volume label depends on which wiki we're on
volumeLabel = "Daily volume",
gameVersion = isOSRS ? 'osrs' : 'rs',
/**
* <doc>
*
* @todo replace `_GEC` wih this
*/
gec = {},
// @todo document each of these
_GEC = {
AIQueue: [],
AILoaded: [],
AIData: [],
addedData: [],
average: parseInt((location.hash.match(/#a=([^#]*)/) || [])[1], 10) || '',
urlCache: {},
annotationsVisible: true
},
/**
* Startup methods
*/
self = {
/**
* Loads and implements any required dependencies
*/
deps: function deps() {
if (!mw.loader.getState('rs.highcharts')) {
mw.loader.implement('rs.highcharts', ['https://code.highcharts.com/stock/highstock.js'], {}, {});
}
if (!mw.loader.getState('rs.highcharts-annotations')) {
mw.loader.implement('rs.highcharts-annotations', ['https://code.highcharts.com/modules/annotations.js'], {}, {});
}
mw.loader.using(['mediawiki.util', 'mediawiki.api', 'rs.highcharts', 'rs.highcharts-annotations', 'oojs-ui-core', 'oojs-ui.styles.icons-media'], self.init);
},
/**
* Initial loading function
*/
init: function init(req) {
window.Highcharts = req('rs.highcharts');
req('rs.highcharts-annotations')(window.Highcharts); //load the annotations module, shouldn't need to store the return value anywhere
(function () {
var newhash = location.hash.replace(/\.([0-9a-f]{2})/gi, function (_, first) {
return String.fromCharCode(parseInt(first, 16));
}).replace(/ /g, '_');
if (newhash && newhash.match(/#[aiz]=/)) {
location.hash = newhash;
}
})();
$('.GEdatachart').attr('id', function (c) {
return 'GEdatachart' + c;
});
$('.GEdataprices').attr('id', function (c) {
return 'GEdataprices' + c;
});
$('.GEChartBox').each(function (c) {
$(this).find('.GEChartItems').attr('id', 'GEChartItems' + c);
});
Highcharts.setOptions({
lang: {
// @todo can this be done with CSS?
resetZoom: null,
numericSymbols: ['K', 'M', 'B', 'T', 'Qd', 'Qt']
}
});
// globals to maintain javascript hrefs
window._GEC = _GEC;
window.popupChart = popupChart;
window.addItem = chart.addItem;
window.removeGraphItem = chart.removeItem;
self.buildPopup();
self.setupCharts();
},
/**
* <doc>
*/
makeOOUI: function makeOOUI(c) {
var averageRangeInput, addItemInput, submitButton, resetButton, annotationsToggle, fieldset, permalink;
averageRangeInput = new OO.ui.NumberInputWidget({
min: 1,
value: 30,
id: 'average' + c
});
averageRangeInput.$element.data('ooui-elem', averageRangeInput);
addItemInput = new OO.ui.TextInputWidget({
id: 'extraItem' + c
});
addItemInput.$element.data('ooui-elem', addItemInput);
submitButton = new OO.ui.ButtonInputWidget({
label: 'Submit',
flags: ['primary', 'progressive']
});
resetButton = new OO.ui.ButtonInputWidget({
label: 'Reset'
});
permalink = new OO.ui.ButtonInputWidget({
label: 'Permanent link',
title: 'Permanent link to the current chart settings and items. Right click to copy the url.',
id: 'GEPermLink' + c
});
permalink.$element.data('ooui-elem', permalink);
permalink.setData('/w/RuneScape:Grand_Exchange_Market_Watch/Chart');
permalink.on('click', function () {
window.open(permalink.getData(), '_blank');
});
averageRangeInput.on('enter', function () {
addItem(c);
});
addItemInput.on('enter', function () {
addItem(c);
});
submitButton.on('click', function () {
addItem(c);
});
resetButton.on('click', function () {
addItemInput.setValue('');
averageRangeInput.setValue(30);
});
annotationsToggle = new OO.ui.ToggleSwitchWidget({
value: true
});
annotationsToggle.on('change', function () {
window._GEC.annotationsVisible = annotationsToggle.getValue();
for (var i = 0; i < window._GEC['chart' + c].annotations.length; i++) {
window._GEC['chart' + c].annotations[i].setVisibility(window._GEC.annotationsVisible);
}
});
fieldset = new OO.ui.FieldsetLayout();
fieldset.addItems([new OO.ui.FieldLayout(averageRangeInput, {
label: 'Average (days)'
}), new OO.ui.FieldLayout(addItemInput, {
label: 'Add new item'
}), new OO.ui.HorizontalLayout({
items: [submitButton, resetButton, permalink]
}), new OO.ui.FieldLayout(annotationsToggle, {
label: 'Show annotations?'
})]);
fieldset.$element.css('width', '50%');
return fieldset.$element;
},
buildPopup: function buildPopup() {
var close;
close = new OO.ui.ButtonWidget({
icon: 'close'
});
close.on('click', function () {
popupChart(false);
});
$('body').append($('<div>').attr('id', 'GEchartpopup').css('display', 'none').append($('<div>').attr('id', 'closepopup').append(close.$element), self.makeOOUI('popup'), $('<div>').attr('id', 'addedItemspopup'), $('<div>').attr('id', 'GEpopupchart')));
},
/**
* <doc>
*/
setupCharts: function setupCharts() {
$('div.GEdatachart').each(function (c) {
var $dataPrices = $('#GEdataprices' + c),
$dataChart = $('#GEdatachart' + c),
dataItem = $dataPrices.attr('data-item'),
isSmall = $dataChart.hasClass('smallChart'),
isMedium = $dataChart.hasClass('mediumChart'),
isIndexChart = /index/i.test(dataItem),
selector = isOSRS ? '.infobox *, .infobar *, .infobox-switch-resources.infobox-resources-Infobox_Item *' : '.infobox *, .infobar *, .rsw-infobox *, .infobox-switch-resources.infobox-resources-Infobox_Item *',
isInfobox = $dataPrices.is(selector),
itemName = dataItem || conf.wgTitle.split('/')[0],
dataList,
yAxis,
zoom;
if (!$dataPrices.length) {
return;
}
// setting up the form and chart elements
if (!isSmall && !isMedium) {
$dataChart.before(self.makeOOUI(c), $('<div>').attr('id', 'addedItems' + c));
}
getData(c, isSmall, isMedium, undefined, function (data) {
var dataList = data[0];
var yAxis = data[1];
if (itemName.toLowerCase() !== 'blank') {
zoom = parseInt((location.hash.match(/#z=([^#]*)/) || [])[1]);
zoom = zoom && zoom <= 6 && zoom >= 0 ? zoom - 1 : zoom === 0 ? 0 : 2;
}
var enlarge = new OO.ui.ButtonWidget({
label: 'Enlarge chart',
icon: 'fullScreen',
id: 'gec-enlarge-' + c
});
enlarge.$element.css("font-size", "13px");
// @todo this doesn't do anything on small charts
// is it supposed to?
//var zoomOut = '<a href="javascript:_GEC.chart' + c + '.zoomOut();" style="text-decoration:underline;color:inherit;font-size:inherit;">Zoom out</a>';
//generating the chart
_GEC['chart' + c] = new Highcharts.StockChart({
chart: {
renderTo: 'GEdatachart' + c,
backgroundColor: 'white',
plotBackgroundColor: 'white',
zoomType: '',
//height: isSmall?210:null,
events: {
redraw: function redraw() {
_GEC.thisid = this.renderTo.id.replace('GEdatachart', '').replace('GEpopupchart', 'popup');
setTimeout(function () {
setChartExtremes(_GEC.thisid);
}, 0);
}
}
},
legend: {
enabled: !isSmall && !isMedium,
backgroundColor: 'white',
align: 'right',
layout: 'vertical',
verticalAlign: 'top',
y: 85
},
responsive: {
rules: [{
condition: {
//maxWidth: 500
},
chartOptions: {
legend: {
align: 'center',
verticalAlign: 'bottom',
layout: 'horizontal'
}
}
}]
},
title: {
text: isSmall || isMedium ? isInfobox || isMedium ? enlarge.$element[0].outerHTML : itemName : 'Grand Exchange Market Watch',
useHTML: true,
style: {
color: 'black',
fontSize: isSmall ? enlarge ? '13px' : '15px' : '18px'
}
},
subtitle: {
text: isSmall ? isInfobox ? '' : enlarge.$element[0].outerHTML : itemName.toLowerCase() == 'blank' ? 'Historical chart' : itemName,
useHTML: true,
y: 50,
style: {
color: '#666',
fontSize: isSmall ? '13px' : '15px'
}
},
rangeSelector: {
enabled: !isSmall && !isMedium,
selected: zoom,
inputBoxStyle: {
right: '15px',
display: isSmall || isMedium ? 'none' : 'block'
},
inputStyle: {
width: '100px'
},
inputDateFormat: "%e-%b-%Y",
buttonTheme: {
"class": 'zoomButton'
},
buttons: [{
type: 'month',
count: 1,
text: '1m'
}, {
type: 'month',
count: 2,
text: '2m'
}, {
type: 'month',
count: 3,
text: '3m'
}, {
type: 'month',
count: 6,
text: '6m'
}, {
type: 'year',
count: 1,
text: '1y'
}, {
type: 'all',
text: 'All'
}]
},
plotOptions: {
series: {
enableMouseTracking: !isSmall,
dataGrouping: {
dateTimeLabelFormats: {
day: ['%A, %e %B %Y', '%A, %e %B', '-%A, %e %B %Y'],
week: ['Week from %A, %e %B %Y', '%A, %e %B', '-%A, %e %B %Y'],
month: ['%B %Y', '%B', '-%B %Y'],
year: ['%Y', '%Y', '-%Y']
}
}
}
},
tooltip: {
enabled: !isSmall,
valueDecimals: isIndexChart ? 2 : 0,
headerFormat: '<span style="font-size: 12px">{point.key}</span><br/>',
xDateFormat: "%A, %e %B %Y"
},
navigator: {
xAxis: {
dateTimeLabelFormats: {
day: "%e-%b",
week: "%e-%b",
month: "%b-%Y",
year: "%Y"
},
minTickInterval: 24 * 3600 * 1000 //1 day
},
maskFill: 'none',
enabled: !(isSmall || isMedium)
},
credits: {
enabled: false
},
xAxis: [{
lineColor: '#666',
tickColor: '#666',
dateTimeLabelFormats: {
day: "%e-%b",
week: "%e-%b",
month: "%b-%Y",
year: "%Y"
},
minTickInterval: 24 * 3600 * 1000,
//1 day
scrollbar: {
enabled: false,
showFull: false
}
}],
yAxis: yAxis,
series: dataList,
colors: window.GEMWChartColors || ['#4572A7', '#AA4643', '#89A54E', '#80699B', '#3D96AE', '#DB843D', '#92A8CD', '#A47D7C', '#B5CA92'],
annotations: [data[2]]
});
var items = ($('#GEChartItems' + c).html() || '').split(',');
var noAdd = [];
var i;
for (i = 0; i < items.length; i++) {
items[i] = items[i].trim();
if (items[i]) {
addItem(c, items[i]);
} else {
noAdd.push(1);
}
}
if (items.length == noAdd.length && _GEC['chart' + c].series[0].name.toLowerCase() != 'blank') setChartRange(c);
//adjusting the axes extremes (initial load)
setChartExtremes(c);
//loading the chart and additional price info when the page is ready
if ((conf.wgNamespaceNumber == 112 && conf.wgTitle.split('/')[1] == 'Data' || conf.wgPageName == 'RuneScape:Grand_Exchange_Market_Watch/Chart') && location.hash.match('#i=') !== null) {
var hash = location.hash;
items = decodeURIComponent((hash.match(/#i=([^#]*)/) || [])[1] || '').replace(/_/g, ' ').split(',');
for (i = 0; i < items.length; i++) {
if (items[i].match(/^\s*$/) === null) addItem(0, items[i]);
}
}
var $enlargeEle = $("#gec-enlarge-" + c);
if ($enlargeEle.length) {
$enlargeEle.on("click", function () {
popupChart(c);
});
}
;
});
});
}
},
/**
* General helper methods
*/
util = {
/**
* <doc>
*
* @todo replace with $.extend
*
* @param a {object}
* @param b {object} (optional)
*
* @return {object}
*/
cloneObj: function cloneObj(a, b) {
if (_typeof(a) !== 'object') {
return '';
}
if (_typeof(b) !== 'object') {
b = {};
}
for (var key in a) {
if (a.hasOwnProperty(key)) {
b[key] = a[key];
}
}
return b;
},
/**
* Averages prices across a specified time interval
*
* @param arr {array} Array of arrays, where each member of `arr`
* is in the format [time, price]
* Which is how we store the price data
* @example [x-coord, y-coord]
* @param amt {number} Interval to average across in days
* @param round {number} (optional) Number of decimal places to round to
* Defaults to 0
*
* @return {array} Array of arrays, where each member of the return array
* is in the format [time, price] (as above)
* and
*/
avg: function avg(arr, amt, round) {
amt = amt || arr.length;
// convert `round` into a number we can use for rounding
round = Math.pow(10, round || 0);
var avgs = [],
list = [],
i;
// adds each price to `list`
// when `amt` is reached, average the contents of `list`
//
// each iteration after `amt` is reached averages the contents of `list`
// which is continuously being updated as each iteration
// after `amt` is reached replaces a member of `list`
// @example when `i` is 31 the current price replaces `list[1]`
// when `i` is 35 the current price replaces `list[5]`
for (i = 0; i < arr.length; i++) {
list[i % amt] = arr[i][1];
if (i >= amt) {
avgs.push([
// don't modify the time (y-coord)
arr[i][0], Math.round(util.sum(list) / list.length * round) / round]);
}
}
return avgs;
},
/**
* Finds the sum of numbers in an array
* Only called by `util.avg`
*
* @param arr {array} Array of number to find the sum of
*
* @return {number} Sum of the numbers in `arr`
*/
sum: function sum(arr) {
var total = 0,
i;
for (i = 0; i < arr.length; i++) {
total += parseFloat(arr[i], 10);
}
return total;
},
/**
* Rounds and formats numbers
*
* @example 12345 -> 12.3K
* @example 1234567 -> 1.2M
* @example 123456789012 -> 123.4M
*
* @param num {number|string} Number to format
*
* @return {string} Formatted number
*/
toKMB: function toKMB(num) {
// strip commas from number string
// as `parseInt` will interpret them as a decimal separator
// pass numbers and string to `parseInt` to convert floats too
num = parseInt(typeof num === 'string' ? num.replace(/,/g, '') : num, 10);
var neg = num < 0 ? '-' : '';
num = Math.abs(num);
// `1eX` is shorthand for `Math.pow( 10, X )`
if (num >= 1e10) {
num = Math.round(num / 1e8) / 10;
num += 'B';
} else if (num >= 1e7) {
num = Math.round(num / 1e5) / 10;
num += 'M';
} else if (num >= 1e4) {
num = Math.round(num / 100) / 10;
num += 'K';
}
return rs.addCommas(neg + num);
},
/**
* Capitalises first character of a string
*
* @source <https://stackoverflow.com/a/1026087>
*
* @param str {string}
*
* @return {string}
*/
ucFirst: function ucFirst(str) {
return str.charAt(0).toUpperCase() + str.slice(1);
},
/**
* Sort data points in the graph data before passing it to the charts api
*/
sortPoints: function sortPoints(a, b) {
a = a.replace(/'/g, '').split(':')[0];
b = b.replace(/'/g, '').split(':')[0];
return a - b;
}
},
/**
* Chart methods
*/
chart = {
/**
* <doc>
*
* @param id {string|number}
* @param match {string} is normally the 'line' that isn't an item's price data
* such as average or volume
*
* @return {number}
*/
getSeriesIndex: function getSeriesIndex(id, match) {
var chart = _GEC['chart' + id],
series = chart.series,
i;
if (chart) {
for (i = 0; i < series.length; i++) {
if (series[i].name.match(match)) {
return i;
}
}
return -1;
}
// @todo what happens if !chart
},
/**
* Creates a URL with preset options
*
* @todo change to url params
* @todo document the individual params/options
*
* @param id {number|string}
*
* @return {string}
*/
permLinkUrl: function permLinkUrl(id) {
var chart = _GEC['chart' + id],
xt = chart.xAxis[0].getExtremes(),
series = chart.series,
minDate = new Date(xt.min).toDateString().split(' ').slice(1).join('_'),
maxDate = new Date(xt.max).toDateString().split(' ').slice(1).join('_'),
inputAvg = $('#average' + id).data('ooui-elem').getNumericValue(),
urlHash = '#t=' + minDate + ',' + maxDate,
items = '',
i;
if (!isNaN(inputAvg)) {
urlHash += '#a=' + inputAvg;
}
for (i = 0; i < series.length; i++) {
if (series[i].name == 'Navigator' || series[i].name.match('average')) {
continue;
}
// separate items with commas
if (items) {
items += ',';
}
// @todo url encode this?
items += series[i].name.replace(/ /g, '_');
}
urlHash += '#i=' + items;
// @todo hide the redirect h2
return '/w/RuneScape:Grand_Exchange_Market_Watch/Chart' + urlHash;
},
/**
* Add a new item to the chart
*
* @param i
* @param it {string} (optional)
*/
addItem: function addItem(i, it) {
_GEC.chartid = i;
var OOUIextraItemPresent = $('#extraItem' + i).length > 0,
OOUIextraItem = $('#extraItem' + i).data('ooui-elem'),
item = (it || '').trim() || OOUIextraItem.getValue(),
dataItems = ['#addedItems' + i + ' [data-item]', '#GEdataprices' + i + '[data-item]'],
$dataItems = $(dataItems.join(',')).map(function () {
return $(this).attr('data-item').toLowerCase();
}),
$addedItems = $('#addedItems' + i),
id,
data,
series,
seriesIndex,
gecchartid = i,
index;
if (item && item.length) {
index = -1;
for (var i2 = 0; i2 < _GEC.AIQueue.length; i2++) {
if (_GEC.AIQueue[i2] == item.toLowerCase()) {
index = i2;
break;
}
}
if (
// @todo should a number passed to .get()
$dataItems.get().indexOf(item.toLowerCase()) !== -1 || index !== -1) {
if (!it) {
alert(item + ' is already in the graph.');
}
if (OOUIextraItemPresent) {
OOUIextraItem.setValue('');
}
return false;
}
if (OOUIextraItemPresent) {
OOUIextraItem.setDisabled(true);
}
$.get('/api.php', {
action: 'query',
prop: 'revisions',
rvprop: 'content',
format: 'json',
titles: 'Module:Exchange/' + util.ucFirst(item)
}).then(function (data, textStatus) {
var OOUIextraItem = $('#extraItem' + gecchartid).data('ooui-elem'),
pages = data.query.pages;
if (textStatus !== 'success') {
alert('An error occured while loading ' + item);
mw.log(data);
}
var matches = [];
var pageMissing = false;
if (pages[-1]) {
pageMissing = true;
} else {
var exchangeData = pages[Object.keys(pages)[0]].revisions[0]['*'];
matches = exchangeData.match(/itemId\D*(\d*)/);
if (matches.length !== 2) {
pageMissing = true;
}
}
// page not found
if (pageMissing) {
if (OOUIextraItem.getValue().length) {
alert('The item ' + item + ' doesn\'t exist on our Grand Exchange database.');
OOUIextraItem.setDisabled(false).setValue('');
return false;
}
_GEC.AILoaded.push(false);
if (_GEC.AIData.length && _GEC.AIQueue.length == _GEC.AILoaded.length) {
loadChartsQueueComplete(gecchartid);
} else if (!_GEC.AIData.length) {
setChartRange(gecchartid);
}
OOUIextraItem.setDisabled(false).setValue('');
return false;
}
var itemId = matches[1];
return $.getJSON("https://api.weirdgloop.org/exchange/history/" + gameVersion + "/all?compress=true&id=" + itemId);
}).then(function (data, textStatus) {
if (data === false) return;
$.getJSON(mw.util.getUrl('Module:Exchange/' + util.ucFirst(item) + '/annotations.json', {
action: 'raw',
'ctype': 'application/json'
})).then(function (annodata) {
return annodata;
}, function () {
return [];
} //handles the page not existing
).then(function (annodata) {
_GEC.AIData.push({
name: item,
data: Object.values(data)[0],
id: item,
gecchartid: gecchartid,
lineWidth: 2,
annotations: formatAnnoList(annodata, Object.values(data)[0])
});
_GEC.AILoaded.push(item);
if (getSeriesIndex(gecchartid, 'average') !== -1) {
_GEC['chart' + gecchartid].series[getSeriesIndex(gecchartid, 'average')].remove();
}
if (_GEC.AIQueue.length === _GEC.AILoaded.length) {
// This is always true when only 1 item is being loaded.
loadChartsQueueComplete(gecchartid);
}
});
});
_GEC.AIQueue.push({
item: item.toLowerCase(),
chart: gecchartid
});
// @todo when does this happen
/* This happens when there are no further items added to the charts, i.e. when the original item is the only one.
This is indeed a flawed test, since it won't work on GEMW/C, where there is no original item in the chart.
This should be replaced with another test that also works on GEMW/C.
*/
} else if ($addedItems.html().match(/^\s*$/) || conf.wgPageName == 'RuneScape:Grand_Exchange_Market_Watch/Chart' && $addedItems.find('a').length === 1) {
id = i === 'popup' ? $('#GEchartpopup').attr('data-chartid') : i;
getData(id, false, false, i, function (data) {
series = _GEC['chart' + i].series;
seriesIndex = getSeriesIndex(i, 'average');
//remove an average line if it already exists
if (seriesIndex !== -1) {
series[seriesIndex].remove();
}
//add average line when there is only 1 item in the chart
_GEC['chart' + i].addSeries(data[0][1]);
});
}
},
/**
* <doc>
*
* @param c {number|string}
*/
loadQueueComplete: function loadQueueComplete(cin, addeditembyscript) {
var cnum = cin !== 'popup',
//if cin is a number, we're probably at initial load of one/many charts on a page, so we need to iterate over the entire queue
c = cnum ? _GEC.AIQueue.length : cin,
//if not a number, its almost certainly 'popup', for which we only need to reload the popup
id,
chartdata,
isSmall = [],
isMedium = [],
data = [],
i,
index,
itemhash,
$addedItems,
iname,
hadBlank;
if (cnum) {
//this structure repeats throughout the method: if cnum then loop else do once. probably a better way to do this
for (i = 0; i < c; i++) {
isSmall[i] = $('#GEdatachart' + i).hasClass('smallChart');
isMedium[i] = $('#GEdatachart' + i).hasClass('mediumChart');
}
} else {
isSmall = $('#GEdatachart' + c).hasClass('smallChart');
isMedium = $('#GEdatachart' + c).hasClass('mediumChart');
}
if (cnum) {
for (i = 0; i < c; i++) {
if (getSeriesIndex(_GEC.AIQueue[i].chart, volumeLabel) !== -1) {
id = i === 'popup' ? $('#GEchartpopup').attr('data-chartid') : i;
getData(id, true, undefined, undefined, function (data) {
data[1].title.text = 'Price history';
reloadChart(i, {
series: data[0],
yAxis: data[1]
});
});
}
}
} else {
if (getSeriesIndex(c, volumeLabel) !== -1) {
id = c === 'popup' ? $('#GEchartpopup').attr('data-chartid') : c;
getData(id, true, undefined, undefined, function (data) {
data[1].title.text = 'Price history';
reloadChart(c, {
series: data[0],
yAxis: data[1]
});
});
}
}
for (i = 0; i < _GEC.AIData.length; i++) {
index = -1;
for (var i2 = 0; i2 < _GEC.AIQueue.length; i2++) {
if (_GEC.AIQueue[i2].item === (_GEC.AIData[i] || {
name: ''
}).name.toLowerCase()) {
index = i2;
break;
}
}
data[index !== -1 ? index : data.length] = _GEC.AIData[i];
}
// @todo should this be `Array.isArray`
// or should it default to `{}`
// @todo test if isSmall is needed in the conditional
if (cnum) {
for (i = 0; i < c; i++) {
if (data[i] === undefined) continue;
if (isSmall[data[i].gecchartid] && isMedium[data[i].gecchartid] && _typeof(Array.isArray(_GEC.addedData[data[i].gecchartid]))) {
_GEC.addedData[data[i].gecchartid] = [];
}
}
} else {
if ((isSmall || isMedium) && _typeof(Array.isArray(_GEC.addedData[data[c].gecchartid]))) {
_GEC.addedData[data[c].gecchartid] = [];
}
}
for (i = 0; i < data.length; i++) {
if (data[i]) {
_GEC['chart' + data[i].gecchartid].addSeries(data[i]);
if (data[i].annotations && data[i].annotations.labels && data[i].annotations.labels.length) {
_GEC['chart' + data[i].gecchartid].addAnnotation(data[i].annotations);
}
}
if (cnum && isSmall[data[i].gecchartid]) {
_GEC.addedData[data[i].gecchartid][i] = data[i];
}
}
if (cnum) {
for (i = 0; i < c; i++) {
setChartExtremes(data[i].gecchartid);
$('#extraItem' + data[i].gecchartid).data('ooui-elem').setDisabled(false).setValue('');
}
} else {
setChartExtremes(c);
$('#extraItem' + c).data('ooui-elem').setDisabled(false).setValue('');
}
itemhash = (location.hash.match(/#i=[^#]*/) || [])[0] || location.hash + '#i=';
$addedItems = $('#addedItems' + c);
for (i = 0; i < data.length; i++) {
if (!data[i]) {
continue;
}
iname = data[i].name;
if (!$addedItems.text().trim()) {
$addedItems.append('Remove items from graph: ', $('<a>').attr({
href: 'javascript:removeGraphItem("' + iname + '","' + c + '")',
'data-item': iname
}).text(iname));
itemhash = '#i=' + iname;
} else {
$addedItems.append(', ', $('<a>').attr({
href: 'javascript:removeGraphItem("' + iname + '","' + c + '")',
'data-item': iname
}).text(iname));
itemhash += ',' + iname;
}
}
if (location.hash.match(/#i=/)) {
itemhash = location.hash.replace(/#i=[^#]*/, itemhash).replace(/ /g, '_');
} else {
itemhash = location.hash + itemhash;
}
if ((conf.wgNamespaceNumber == 112 && conf.wgTitle.split('/')[1] == 'Data' || conf.wgPageName == 'RuneScape:Grand_Exchange_Market_Watch/Chart') && itemhash.replace('#i=', '').length) {
location.hash = itemhash;
}
_GEC.AIQueue = [];
_GEC.AILoaded = [];
_GEC.AIData = [];
if (cnum) {
for (i = 0; i < c; i++) {
hadBlank = removeGraphItem('Blank', data[i].gecchartid);
if (hadBlank) {
setChartRange(data[i].gecchartid);
}
}
} else {
hadBlank = removeGraphItem('Blank', c);
if (hadBlank) {
setChartRange(c);
}
}
},
/**
* <doc>
*
* @param c {number|string}
*
* @return {boolean}
*/
setRange: function setRange(c) {
var zoom = parseInt((location.hash.match(/#z=([^#]*)/) || [])[1], 10);
zoom = zoom && zoom <= 6 && zoom >= 0 ? zoom - 1 : zoom === 0 ? 0 : 2;
var hash = location.hash;
var hasT = conf.wgNamespaceNumber === 112 && conf.wgTitle.split('/')[1] === 'Data' || conf.wgPageName === 'RuneScape:Grand_Exchange_Market_Watch/Chart';
if (typeof c === 'number' && (hasT && !hash.match('#t=') || !hasT)) {
$('#GEdatachart' + c + ' .zoomButton').eq(zoom).click();
return true;
}
var timespan = decodeURIComponent((hash.match(/#t=([^#]*)/) || [])[1] || '').replace(/_/g, ' ').split(',');
var dates = [new Date(timespan[0]), new Date(timespan[1])];
var d = new Date(timespan[0]);
var extremes = _GEC['chart' + c].xAxis[0].getExtremes();
if (dates[0] !== 'Invalid Date' && dates[1] === 'Invalid Date' && typeof zoom === 'number') {
var button = _GEC['chart' + c].rangeSelector.buttonOptions[zoom];
if (button.type === 'month') {
d.setMonth(d.getMonth() + button.count);
} else if (button.type === 'year') {
d.setYear(d.getFullYear() + button.count);
} else if (button.type === 'all') {
d = new Date(extremes.dataMax);
}
dates[1] = d;
}
if (dates[0] !== 'Invalid Date' && dates[1] !== 'Invalid Date') {
_GEC['chart' + c].xAxis[0].setExtremes(dates[0].getTime(), dates[1].getTime());
return true;
}
return false;
},
/**
* <doc>
*
* @param c {number|string}
* @param change {object}
*/
reload: function reload(c, change) {
var options = _GEC['chart' + c].options;
if (!options) {
// @todo do we need to return `false` here
// @todo when does this happen
return false;
}
$.extend(options, change);
_GEC['chart' + c] = new Highcharts.StockChart(options);
},
/**
* <doc>
*
* @param item {string}
* @param c {number|string}
*
* @return {boolean}
*/
removeItem: function removeItem(item, c) {
var series = _GEC['chart' + c].series,
id,
i,
newhash,
data;
// find the item we want to remove
for (i = 0; i < series.length; i++) {
if (series[i].name.match(item)) {
id = i;
}
}
// @todo when does this happen
// when we can't find the item?
if (typeof id !== 'number') {
return false;
}
// remove item from url hash
newhash = location.hash.replace(/_/g, ' ').replace(new RegExp('(#i=[^#]*),?' + item, 'i'), '$1').replace(/,,/g, ',').replace(/,#/g, '#').replace(/#i=,/g, '#i=').replace(/#i=($|#)/, '$1').replace(/ /g, '_');
if (newhash.replace('#i=', '').length) {
location.hash = newhash;
} else if (location.hash.length) {
location.hash = '';
}
// remove the item from the chart
series[id].remove();
// reset extremes?
setChartExtremes(c);
// @todo can we cache #addedItems somehow
// remove item from list at top of graph
$('#addedItems' + c + ' [data-item="' + item + '"]').remove();
// cleanup list
$('#addedItems' + c).html($('#addedItems' + c).html().replace(/, , /g, ', ').replace(/, $/, '').replace(': , ', ': '));
// if the list is empty show average, volume and item stats again
if (!$('#addedItems' + c + ' [data-item]').length) {
$('#addedItems' + c).empty();
id = c == 'popup' ? $('#GEchartpopup').attr('data-chartid') : c;
data = getData(id, false, false, 'popup', function (data) {
reloadChart(c, {
series: data[0],
yAxis: data[1]
});
});
}
return true;
},
/**
* <doc>
*
* @param i {number|string}
*/
popup: function popup() {},
/**
* <doc>
*
* @param i
*/
setExtremes: function setExtremes(i) {
var ch = _GEC['chart' + i],
exts = _GEC['chart' + i].yAxis[0].getExtremes();
if (exts.dataMin * 0.95 !== exts.userMin || exts.dataMax * 1.05 !== exts.userMax) {
ch.yAxis[0].setExtremes(exts.dataMin * 0.95, exts.dataMax * 1.05);
if (ch.yAxis[2]) {
exts = ch.yAxis[1].getExtremes();
ch.yAxis[1].setExtremes(0, exts.dataMax * 1.05);
}
}
if (i === 'popup') {
// @todo use onclick event
$('#GEPermLink' + i).data('ooui-elem').setData(chartPermLinkUrl(i));
}
},
/**
* <doc>
*
* @param c {number|string}
* @param isSmall {boolean}
* @param avginput {number|string} (optional)
* number component of input element used for altering the average interval
* when the interval is in days
* when is this different to `c`?
*
* @return {array} 2 item array containing X and Y respectively
* @todo expand on what X and Y are
*/
getData: function getData() {}
},
// map old functions to new locations until uses are fixed
getSeriesIndex = chart.getSeriesIndex,
chartPermLinkUrl = chart.permLinkUrl,
addItem = chart.addItem,
removeGraphItem = chart.removeItem,
reloadChart = chart.reload,
setChartRange = chart.setRange,
setChartExtremes = chart.setExtremes,
loadChartsQueueComplete = chart.loadQueueComplete;
// popupChart = chart.popup;
// getData = chart.getData;
// chart-related general functions
function popupChart(i) {
var $popup = $('#GEchartpopup'),
$overlay = $('#overlay'),
options,
data,
n;
if (!$popup.length) {
return false;
}
if ($overlay.length) {
$overlay.toggle();
} else {
$popup.before($('<div>').attr('id', 'overlay').css('display', 'block'));
$overlay = $('#overlay');
}
$overlay.on('click', function () {
popupChart(false);
});
if (typeof i === 'number') {
$(document).keydown(function (e) {
// Esc
if (e.which === 27) {
popupChart(false);
}
});
} else {
// @todo only remove our event
$(document).off('keydown');
}
if (typeof i === 'boolean' && !i) {
$popup.hide();
$('#addedItemspopup').html('');
} else {
$popup.toggle();
}
if (typeof i === 'number' && $popup.attr('data-chartid') !== i) {
$('#averagepopup').data('ooui-elem').setValue(_GEC.average);
$popup.attr('data-chartid', i);
options = {};
getData(i, false, false, 'popup', function (data) {
var dataList = data[0];
var yAxis = data[1];
// @todo can this be replaced with $.extend?
// @todo what is this supposed to do?
util.cloneObj(_GEC['chart' + i].options, options);
options.chart.renderTo = 'GEpopupchart';
options.legend.enabled = true;
options.title.text = 'Grand Exchange Market Watch';
options.title.style.fontSize = '18px';
options.subtitle.text = options.series[0].name;
options.subtitle.y = 35;
options.subtitle.style.fontSize = '15px;';
options.chart.zoomType = '';
options.rangeSelector.enabled = true;
options.rangeSelector.inputBoxStyle.display = 'block';
options.plotOptions.series.enableMouseTracking = true;
options.tooltip.enabled = true;
options.navigator.enabled = true;
options.credits.enabled = false;
options.series = [{}];
options.series = _GEC.addedData[i] ? [dataList[0]] : dataList;
options.yAxis = yAxis;
options.annotations = [data[2]];
_GEC.chartpopup = new Highcharts.StockChart(options);
if (_GEC.addedData[i]) {
for (n = 0; n < _GEC.addedData[i].length; n++) {
_GEC.chartpopup.addSeries(_GEC.addedData[i][n]);
}
}
setChartExtremes('popup');
_GEC.chartpopup.redraw();
$('#GEPermLinkpopup').data('ooui-elem').setData(chartPermLinkUrl('popup'));
});
}
}
function rg(num) {
var colour = 'red';
if (num > 0) {
colour = 'green';
} else if (num === 0) {
colour = 'blue';
}
return colour;
}
function formatAnnoList(annoData, prices) {
var annoList = {
labelOptions: {
verticalAlign: 'bottom',
overflow: 'justify',
y: -5
},
draggable: '',
visible: window._GEC.annotationsVisible,
labels: []
};
for (var i = 0; i < annoData.length; i++) {
var ad = annoData[i];
var label = ad.title;
if (!(ad.link === undefined || ad.link === null || ad.link === '')) {
var labelUrl;
if (ad.link.startsWith('https://') || ad.link.startsWith('http://')) {
labelUrl = ad.link;
} else {
labelUrl = mw.util.getUrl(ad.link);
}
label = '<a href="' + labelUrl + '" target="_blank">' + ad.title + '</a>';
}
var ts = Date.parse(ad.timestamp);
if (!isNaN(ts)) {
var priceAtTs = 0;
for (var i2 = 0; i2 < prices.length; i2++) {
if (prices[i2][0] === ts) {
priceAtTs = prices[i2][1];
break;
}
if (prices[i2][0] > ts) {
if (i2 > 0) {
if (prices[i2 - 1][0] < ts) {
priceAtTs = prices[i2][1];
break;
}
} else {
priceAtTs = prices[i2][1];
break;
}
}
}
var anno = {
text: label,
point: {
xAxis: 0,
yAxis: 0,
x: ts,
y: priceAtTs
}
};
annoList.labels.push(anno);
}
}
return annoList;
}
function getData(cin, isSmall, isMedium, avginput, callback) {
var c = cin === 'popup' ? $('#GEchartpopup').attr('data-chartid') : cin,
$dataPrices = $('#GEdataprices' + c),
dataItem = $dataPrices.attr('data-item'),
dataItemId = $dataPrices.attr('data-itemId') || 'GE ' + dataItem,
isIndexChart = /index/i.test(dataItem),
itemName = dataItem || conf.wgTitle.split('/')[0],
ch = _GEC['chart' + c],
chartLoaded = !!(ch && ch.series && ch.series.length),
prices = [],
i,
data = [],
thisprice,
volumes = [],
dataList,
inputAvg,
newhash,
yAxis,
chartPageData;
// happens when the first chart isSmall
// and the average input id is actually the popup chart
// the chart's id is popup, but the input's id is 0
avginput = avginput || cin;
var pricesToDataList = function pricesToDataList(inputData) {
var prices = inputData[0],
annoData = inputData[1];
_GEC.urlCache[url] = prices;
_GEC.urlCache[annoUrl] = annoData;
prices = Object.values(prices)[0];
var volumeMultiplier = 1; //isOSRS ? 1 : 1000000
for (i = 0; i < prices.length; i++) {
data.push([
// time
prices[i][0],
// @todo should this be parseInt?
// price
prices[i][1]]);
if (prices[i][2] && !isSmall) {
volumes.push([
// time
prices[i][0],
// volume
// volumes are in millions
prices[i][2] * volumeMultiplier]);
}
}
// datalist's elements are essentially each line on the chart
// so price, 30-day-average and volume
dataList = [{
name: itemName,
data: data,
lineWidth: isSmall ? 2 : 3
}];
if (itemName.toLowerCase() === 'blank' && !chartLoaded) {
dataList[0].color = '#000000';
}
if (!isSmall && !isMedium && (itemName.toLowerCase() !== 'blank' || chartLoaded)) {
inputAvg = $('#average' + avginput).data('ooui-elem').getNumericValue();
// @todo should this be isNaN?
if (inputAvg) {
newhash = location.hash.replace(/#a=[^#]*|$/, '#a=' + inputAvg).replace(/ /g, '_');
if (newhash.length) {
location.hash = newhash;
}
}
inputAvg = inputAvg || 30;
dataList.push({
name: inputAvg + '-day average',
data: util.avg(data, inputAvg, isIndexChart ? 2 : 0),
lineWidth: 2,
dashStyle: 'shortdash'
});
if (volumes.length >= 10) {
dataList.push({
name: volumeLabel,
data: volumes,
type: 'area',
color: '#cc8400',
fillColor: {
linearGradient: {
x1: 0,
y1: 0,
x2: 0,
y2: 1
},
stops: [[0, '#ffa500'], [1, 'white']]
},
// display on separate y-axis
yAxis: 1
});
}
}
// create y-axis for price data
yAxis = {
title: {
text: isSmall ? null : isIndexChart ? 'Index history' : 'Price history',
offset: 60,
rotation: 270,
style: {
color: 'black',
fontSize: '12px'
}
},
opposite: false,
labels: {
align: 'right',
x: -8,
y: 4
},
allowDecimals: false,
// 1 coin
minTickInterval: 1,
showLastLabel: 1,
lineWidth: 1,
lineColor: '#E0E0E0'
};
// volume data is plotted on a seperate y-axis
if (volumes.length >= 10 && !isSmall) {
// set height to allow room for second y-axis
yAxis.height = 200;
// convert to array and add volume data
yAxis = [yAxis, {
title: {
text: volumeLabel,
offset: 60,
rotation: 270,
style: {
color: 'black',
fontSize: '12px'
}
},
opposite: false,
labels: {
align: 'right',
x: -8,
y: 4
},
showEmpty: 0,
showLastLabel: 1,
offset: 0,
lineWidth: 1,
lineColor: '#E0E0E0',
height: 50,
top: 325,
min: 0
}];
}
var annoList = {};
if (!isSmall) {
annoList = formatAnnoList(annoData, prices);
}
return [dataList, yAxis, annoList];
};
var isPopup = !isSmall && !isMedium;
var dataType = isPopup ? 'all' : 'sample';
var url = "https://api.weirdgloop.org/exchange/history/" + gameVersion + "/" + dataType + "?compress=true&id=" + dataItemId;
var annoUrl = mw.util.getUrl('Module:Exchange/' + itemName + '/annotations.json', {
action: 'raw',
'ctype': 'application/json'
});
var pricesPromise;
if (chartLoaded && itemName.toLowerCase() === 'blank') {
chartPageData = _GEC['chart' + c].series[getSeriesIndex(c, $('#addedItems' + c).find('a').data('item'))];
for (i = 0; i < chartPageData.xData.length; i++) {
prices.push(chartPageData.xData[i] + ':' + chartPageData.yData[i]);
}
pricesPromise = Promise.resolve(prices);
} else {
if (_GEC.urlCache[url]) {
return callback(pricesToDataList([_GEC.urlCache[url], _GEC.urlCache[annoUrl]]));
}
Promise.all([
// combine these two calls
// this fetches the price data from the API
// and the annotation data from the wiki
// the wiki annotation page may not exist, which returns the second part of the then
$.getJSON(url), $.getJSON(annoUrl).then(function (d) {
return d;
}, function () {
return [];
}) //on exception, just use an empty list
]).then(pricesToDataList).then(callback);
}
}
$(self.deps);