MediaWiki:Gadget-GECharts-sandbox.js: Difference between revisions

no edit summary
No edit summary
No edit summary
 
Line 27:
* 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>
*
* @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
_GEC = { min: 1,
AIQueuevalue: []30,
AILoadedid: [],'average' + c
AIData: [],});
averageRangeInput.$element.data('ooui-elem', averageRangeInput);
addedData: [],
addItemInput = new OO.ui.TextInputWidget({
average: parseInt((location.hash.match(/#a=([^#]*)/) || [])[1], 10) || '',
urlCacheid: {},'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')));
},
 
/**
* Startup methods<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 = if (!mw$dataChart.loader.getStatehasClass('rs.highchartssmallChart')) {,
isMedium = mw.loader$dataChart.implementhasClass('mediumChart'),
isIndexChart = 'rs/index/i.highcharts'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) mw.loader.implement({
$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
/**
* Initial// loading function 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) {
return _GEC['GEdatachartchart' + c;] = new Highcharts.StockChart({
});chart: {
$( renderTo: '.GEdatapricesGEdatachart').attr('id', function+ (c) {,
returnbackgroundColor: 'GEdatapriceswhite' + c;,
}); plotBackgroundColor: 'white',
$('.GEChartBox').each(function (c) {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({
lang: setTimeout(function () {
// @todo can this be done with CSS?setChartExtremes(_GEC.thisid);
}, resetZoom: null,0);
numericSymbols: ['K', 'M', 'B', 'T', 'Qd', 'Qt'],
}
});
},
 
//legend: globals to maintain javascript hrefs{
window._GEC = _GEC;enabled: !isSmall && !isMedium,
window.popupChart = popupChart;backgroundColor: 'white',
window.addItem = chart.addItem;align: 'right',
window.removeGraphItem = chart.removeItem;layout: 'vertical',
verticalAlign: 'top',
 
self.buildPopup(); y: 85
self.setupCharts();},
}, responsive: {
rules: [{
 
/** condition: {
* <doc> //maxWidth: 500
*/ },
makeOOUI: function (c) chartOptions: {
legend: {
var averageRangeInput, addItemInput, submitButton, resetButton, annotationsToggle, fieldset, permalink;
averageRangeInput = new OO.ui.NumberInputWidget({ align: 'center',
min verticalAlign: 1'bottom',
value layout: 30,'horizontal'
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 () {
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;
}
}]
 
// setting up the form and chart elements},
if (!isSmall && !isMedium)title: {
text: isSmall || isMedium ? isInfobox || isMedium ? enlarge.$element[0].outerHTML : itemName : 'Grand Exchange Market Watch',
$dataChart.before(
useHTML: self.makeOOUI(c)true,
style: $('<div>'){
color: .attr('idblack', 'addedItems' + c)
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) {
var dataList = data[0]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 *= [],
* @param a {object}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 return a - b;`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);
/**
* <doc>},
/**
* 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, * @return {number}'').split(':')[0];
return a - */b;
}
getSeriesIndex: function (id, match) {
},
var chart = _GEC['chart' + id],
/**
series = chart.series,
* Chart i;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 return -1;commas
if (items) }{
items += ',';
}
 
// @todo what happensurl ifencode !chartthis?
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 @todo documentto the individual params/optionschart
*
* @param id {number|string}i
* @param it {string} *(optional)
* @return {string}/
addItem: function addItem(i, it) {
*/
_GEC.chartid = i;
permLinkUrl: function (id) {
var chartOOUIextraItemPresent = _GEC[$('chart#extraItem' + id]i).length > 0,
OOUIextraItem = $('#extraItem' + i).data('ooui-elem'),
xt = chart.xAxis[0].getExtremes(),
item = (it || '').trim() || series = chartOOUIextraItem.seriesgetValue(),
dataItems = ['#addedItems' + i + ' [data-item]', '#GEdataprices' + i + '[data-item]'],
minDate = (new Date(xt.min))
$dataItems = $(dataItems.join(',')).map(function () {
.toDateString()
return $(this).splitattr(' data-item').toLowerCase();
.slice(1}),
$addedItems = .join$('_#addedItems' + i),
id,
maxDate = (new Date(xt.max))
.toDateString()data,
.split(' ')series,
.slice(1)seriesIndex,
gecchartid = .join('_')i,
index;
inputAvg = $('#average' + id).data('ooui-elem').getNumericValue(),
if (item && item.length) {
urlHash = '#t=' + minDate + ',' + maxDate,
itemsindex = '',-1;
for (var i2 = 0; i2 < i_GEC.AIQueue.length; i2++) {
if (_GEC.AIQueue[i2] == item.toLowerCase()) {
 
ifindex (!isNaN(inputAvg))= {i2;
urlHash += '#a=' + inputAvgbreak;
}
}
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 fornot (i = 0; i < series.length; i++) {found
if (pageMissing) {
if (series[i].name == 'Navigator' || series[i].name.match('average')) {
if (OOUIextraItem.getValue().length) continue;{
alert('The item ' + item + ' doesn\'t exist on our Grand Exchange database.');
}
OOUIextraItem.setDisabled(false).setValue('');
 
return // separate items with commasfalse;
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);
 
//} @todoelse hideif the(!_GEC.AIData.length) redirect h2{
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;
 
/** }
* <doc>var itemId = matches[1];
return $.getJSON("https://api.weirdgloop.org/exchange/history/" + gameVersion + "/all?compress=true&id=" + itemId);
*
}).then(function *(data, @param ctextStatus) {number|string}
*/ 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) id,{
return chartdata,annodata;
}, function () isSmall = [],{
isMedium =return [],;
} //handles the page not data = [],existing
).then(function (annodata) i,{
index,_GEC.AIData.push({
name: itemhashitem,
data: $addedItemsObject.values(data)[0],
id: inameitem,
gecchartid: hadBlank;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
for (i = 0}); i < c; i++) {
isSmall[i] = $('#GEdatachart' + i)_GEC.AILoaded.hasClasspush('smallChart'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) {
for loadChartsQueueComplete(i = 0gecchartid); 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]
});
});
}
}
});
});
_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 === var'popup' zoom? = parseInt$((location'#GEchartpopup').hash.matchattr(/#z=([^#]*)/'data-chartid') ||: [])[1], 10)i;
getData(id, false, false, i, function (data) {
zoom = zoom && zoom <= 6 && zoom >= 0 ? zoom - 1 : (zoom === 0 ? 0 : 2);
series = var_GEC['chart' hash =+ locationi].hashseries;
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 .replace(/_/g,seriesIndex '!== '-1) {
series[seriesIndex].splitremove(',');
}
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') {
var button = _GEC['chart' + ci].rangeSelector.buttonOptionsaddSeries(data[0][zoom1]);
});
}
},
/**
* <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 < dseries.setMonth(d.getMonth()length; i++) button.count);{
} else if (buttonseries[i].type === 'year'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 dates[1] = d;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 return false;chart
},series[id].remove();
// reset extremes?
setChartExtremes(c);
 
// @todo can we cache #addedItems somehow
/**
// remove item *from <doc>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') : return falsec;
data = getData(id, false, false, 'popup', function (data) {
}
reloadChart(c, {
 
$.extend(options,series: change);data[0],
yAxis: data[1]
_GEC['chart' + c] = new Highcharts.StockChart(options);
},);
});
 
/**}
return * <doc>true;
*},
/**
* @param item {string}
* <doc>
* @param c {number|string}
*
* @returnparam i {booleannumber|string}
*/
removeItempopup: function popup(item, c) {},
/**
var series = _GEC['chart' + c].series,
* id,<doc>
i,*
* @param newhash,i
data;*/
setExtremes: function setExtremes(i) {
 
var ch = _GEC['chart' + i],
// find the item we want to remove
exts = _GEC['chart' + for (i = ].yAxis[0; i < series].lengthgetExtremes(); i++) {
if (exts.dataMin * 0.95 !== exts.userMin || exts.dataMax * 1.05 if!== (series[i]exts.name.match(item)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;
// getData = chart.getData;
Line 1,278 ⟶ 1,077:
 
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);
}
});
} 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) {
var colour = 'red';
if (num > 0) {
 
ifcolour (num= > 0) {'green';
} else if colour(num === 0) 'green';{
} else if (numcolour === 0) {'blue';
}
colour = 'blue';
return colour;
}
 
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>';
}
for (var its = 0; i<annoDataDate.lengthparse(ad.timestamp); i++){
if (!isNaN(ts)) {
var ad = annoData[i];
var labelpriceAtTs = ad.title0;
for (var ifi2 (!(ad.link === undefined0; ||i2 ad.link< === null || adprices.linklength; ===i2++) '')){
if (prices[i2][0] === ts) var labelUrl;{
priceAtTs = prices[i2][1];
if (ad.link.startsWith('https://') || ad.link.startsWith('http://')) {
labelUrl = ad.linkbreak;
} else {
labelUrl = mw.util.getUrl(ad.link);
}
label = '<a href="'+labelUrl+'" target="_blank">'+ad.title+'</a>';
}
varif ts(prices[i2][0] => Date.parse(ad.timestampts); {
if (!isNaN(ts)i2 > 0) {
varif priceAtTs(prices[i2 =- 1][0;] < ts) {
for (var i2priceAtTs = 0; prices[i2 < prices.length][1]; i2++) {
if (prices[i2][0] === ts) {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;
}
}
}
} var anno =else {
priceAtTs = text: label,prices[i2][1];
point: {break;
xAxis: 0,}
yAxis: 0,}
x: ts,}
var anno = y: priceAtTs{
text: }label,
point: };{
xAxis: annoList.labels.push(anno);0,
yAxis: 0,
x: ts,
y: priceAtTs
}
};
annoList.labels.push(anno);
}
}
return annoList;
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
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 = // volumenewhash;
// 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: dataList = [{
text: isSmall ? null : isIndexChart ? 'Index history' : 'Price history',
name: itemName,
dataoffset: data60,
lineWidth: isSmall ? 2 rotation: 3270,
}];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 // @todo should this bevolume isNaN?data
yAxis = if (inputAvg)[yAxis, {
title: {
newhash = location.hash
text: volumeLabel,
.replace(/#a=[^#]*|$/, '#a=' + inputAvg)
offset: .replace(/ /g60, '_');
rotation: 270,
 
if (newhash.length)style: {
color: location.hash = newhash;'black',
fontSize: }'12px'
}
},
 
opposite: false,
inputAvg = inputAvg || 30;
labels: dataList.push({
namealign: inputAvg + '-day averageright',
x: -8,
data: util.avg(data, inputAvg, isIndexChart ? 2 : 0),
lineWidthy: 2,4
dashStyle: 'shortdash'},
showEmpty: });0,
showLastLabel: 1,
 
offset: 0,
if (volumes.length >= 10) {
lineWidth: dataList.push({1,
namelineColor: volumeLabel'#E0E0E0',
dataheight: volumes50,
typetop: 'area'325,
colormin: '#cc8400',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 = {};
var isPopup =if (!isSmall) && !isMedium;{
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);