From 5ecdf6d197e360804c3bb6c28ccaa28cc28ad3b9 Mon Sep 17 00:00:00 2001 From: Matthew Fettig Date: Tue, 29 Nov 2016 14:20:15 -0800 Subject: [PATCH] fixes for IE --- .../external-markets/external-markets.less | 2 +- src/app/manage-currencies/manage.less | 14 +- src/app/markets/markets.js | 61 +- src/app/markets/markets.less | 8 +- src/app/markets/markets.tpl.html | 2 +- src/common/priceChart.js | 673 +++++++------- src/common/totalHistory.js | 859 ++++++++++-------- src/index.html | 1 + src/less/jquery.selectbox.less | 177 ++-- src/less/main.less | 6 +- src/less/orderBook.less | 2 +- src/less/tradeFeed.less | 4 +- 12 files changed, 935 insertions(+), 874 deletions(-) diff --git a/src/app/external-markets/external-markets.less b/src/app/external-markets/external-markets.less index d8f17f9..aec0fb1 100644 --- a/src/app/external-markets/external-markets.less +++ b/src/app/external-markets/external-markets.less @@ -38,7 +38,7 @@ } .external-markets .total span { - font-family: "Open Sans Light" + font-family: "Open Sans Light", "Open Sans", Arial } .external-markets .total small { diff --git a/src/app/manage-currencies/manage.less b/src/app/manage-currencies/manage.less index 7c6e1a5..191454e 100644 --- a/src/app/manage-currencies/manage.less +++ b/src/app/manage-currencies/manage.less @@ -1,7 +1,7 @@ //manage currencies & gateways .manage_wrapper { - font-family: "Open Sans" !important; + font-family: "Open Sans", Arial !important; h5 { margin-bottom: 20px; @@ -70,14 +70,14 @@ #curr_list { min-height: 240px; - + input { margin: 5px 5px 10px 10px; cursor: pointer; } } - #irba_gateway_curr_list text { + #irba_gateway_curr_list text { padding: 5px; } @@ -180,14 +180,14 @@ a { color: #346aa9; } - } + } } } -//manage gateway specific +//manage gateway specific .manage_gateways { @@ -202,8 +202,8 @@ } @media (min-width: 1200px) { - + .manage_gateways .removeBtn { margin-right: 95px; } -} \ No newline at end of file +} diff --git a/src/app/markets/markets.js b/src/app/markets/markets.js index d59d1e9..22cc319 100644 --- a/src/app/markets/markets.js +++ b/src/app/markets/markets.js @@ -135,6 +135,7 @@ function MarketsCtrl($scope, $state, $location, gateways) { var updateMode = '' var dropdownA var dropdownB + var toCSV // set up the price chart var priceChart = new PriceChart({ @@ -166,33 +167,6 @@ function MarketsCtrl($scope, $state, $location, gateways) { url: API }) - var toCSV = d3.select('#toCSV') - .on('click', function() { - if (toCSV.attr('disabled')) { - return - } - - var data = priceChart.getRawData() - var list = [] - - for (var i = 0; i < data.length; i++) { - list.push(JSON.parse(JSON.stringify(data[i]))) - } - - var csv = jsonToCSV(list) - if (Modernizr.prefixed('requestFileSystem', window)) { - var blob = new Blob([csv], {'type': 'application/octet-stream'}) - this.href = window.URL.createObjectURL(blob) - - } else { - this.href = 'data:text/csvcharset=utf-8,' + escape(csv) - } - - this.download = $scope.base.currency + '_' + - $scope.counter.currency + '_historical.csv' - this.target = '_blank' - }) - /** * updateScopeAndStore */ @@ -466,6 +440,39 @@ function MarketsCtrl($scope, $state, $location, gateways) { tradeFeed.loadPair($scope.base, $scope.counter) } + /** + * downloadCSV + */ + + function downloadCSV() { + if (toCSV.attr('disabled')) { + return + } + + var interval = getInterval() + var range = getRange() + + var csvURL = API + '/exchanges/' + $scope.base.currency + + ($scope.base.issuer ? '+' + $scope.base.issuer : '') + + '/' + $scope.counter.currency + + ($scope.counter.issuer ? '+' + $scope.counter.issuer : '') + + '?limit=1000&format=csv' + + '&interval=' + interval.multiple + interval.interval + + if (range.name === 'custom') { + csvURL += '&start=' + $scope.start + csvURL += '&end=' + $scope.end + } else { + csvURL += '&start=' + moment.utc().format() + csvURL += '&end=' + moment.utc(range.offset(moment())).format() + } + + d3.select(this).attr('href', csvURL) + } + + toCSV = d3.select('#toCSV') + .on('click', downloadCSV) + // set up flip button d3.select('#flip').on('click', function() { var swap = $scope.counter diff --git a/src/app/markets/markets.less b/src/app/markets/markets.less index 4870682..ebdf016 100644 --- a/src/app/markets/markets.less +++ b/src/app/markets/markets.less @@ -9,8 +9,8 @@ } img { - width: 15px; - height: auto; + width: 18px; + height: 18px; } } @@ -96,7 +96,7 @@ .downloadIcon { width: 18px; - height: auto; + height: 18px; } @@ -106,7 +106,7 @@ color:#666; display:inline-block; text-align:center; - font-family:"Open Sans"; + font-family:"Open Sans", Arial; padding: 12px 6px 0; } diff --git a/src/app/markets/markets.tpl.html b/src/app/markets/markets.tpl.html index ac1ec7c..307c0ec 100644 --- a/src/app/markets/markets.tpl.html +++ b/src/app/markets/markets.tpl.html @@ -94,7 +94,7 @@
Live Market Data
- +
diff --git a/src/common/priceChart.js b/src/common/priceChart.js index 4a01943..160dee3 100644 --- a/src/common/priceChart.js +++ b/src/common/priceChart.js @@ -4,90 +4,90 @@ no-use-before-define: 0 } */ -'use strict'; +'use strict' if (!LOADER_PNG) { - var LOADER_PNG = 'assets/images/rippleThrobber.png'; + var LOADER_PNG = 'assets/images/rippleThrobber.png' } function PriceChart(options) { - var self = this; - var xScale = d3.time.scale(); - var priceScale = d3.scale.linear(); - var volumeScale = d3.scale.linear(); - var xAxis = d3.svg.axis().scale(xScale); + var self = this + var xScale = d3.time.scale() + var priceScale = d3.scale.linear() + var volumeScale = d3.scale.linear() + var xAxis = d3.svg.axis().scale(xScale) var volumeAxis = d3.svg.axis() .scale(volumeScale) .orient('left') - .tickFormat(d3.format('s')); + .tickFormat(d3.format('s')) var priceAxis = d3.svg.axis().scale(priceScale) - .orient('right'); - var apiHandler = new ApiHandler(options.url); - var isLoading = true; - var lineData = []; - var liveFeed; - - var wrap; - var div; - var h; - var w; - var staticHeight; - var svg; - var gEnter; - var gradient; - var hover; - var horizontal; - var focus; - var status; - var details; - var loader; - var base; - var counter; - var startTime; - var endTime; - var type; - var chartInterval; - var intervalSeconds; - var multiple; - var lastCandle; + .orient('right') + var apiHandler = new ApiHandler(options.url) + var isLoading = true + var lineData = [] + var liveFeed + + var wrap + var div + var h + var w + var staticHeight + var svg + var gEnter + var gradient + var hover + var horizontal + var focus + var status + var details + var loader + var base + var counter + var startTime + var endTime + var type + var chartInterval + var intervalSeconds + var multiple + var lastCandle /** * tz */ function tz(dateInput) { - var dateObject = dateInput.toDate() || new Date(); - var dateString = String(dateObject); + var dateObject = dateInput.toDate() || new Date() + var dateString = String(dateObject) var tzAbbr = ( // Works for the majority of modern browsers dateString.match(/\(([^\)]+)\)$/) || // IE outputs date strings in a different format: dateString.match(/([A-Z]+) [\d]{4}$/) - ); + ) if (tzAbbr) { // Old Firefox uses the long timezone name (e.g., 'Central // Daylight Time' instead of 'CDT') - tzAbbr = tzAbbr[1].match(/[A-Z]/g).join(''); + tzAbbr = tzAbbr[1].match(/[A-Z]/g).join('') } // return GMT offset if all else fails if (!tzAbbr && /(GMT\W*\d{4})/.test(dateString)) { - return RegExp.$1; + return RegExp.$1 } - return tzAbbr; + return tzAbbr } // format an amount if ripple-lib is present function rippleAmount(amount, currency) { if (typeof ripple === 'undefined' || !ripple.Amount) { - return amount; + return amount } return ripple.Amount.from_human(amount + ' ' + currency) - .to_human({max_sig_digits: 6}); + .to_human({max_sig_digits: 6}) } @@ -97,60 +97,59 @@ function PriceChart(options) { case 'da': return date.utc().format('MMMM D') + ' (' + date.utc().format('hh:mm A') + - ' UTC)'; + ' UTC)' case 'mo': - return date.utc().format('MMMM YYYY') + ' UTC'; + return date.utc().format('MMMM YYYY') + ' UTC' default: return date.format('MMMM D') + - ' · ' + date.format('hh:mm:ss A') + ' ' + tz(date); + ' · ' + date.format('hh:mm:ss A') + ' ' + tz(date) } } // report status to the user, or hide it function setStatus(string) { - status.html(string).style('opacity', 1); + status.html(string).style('opacity', 1) if (string) { - loader.transition().duration(10).style('opacity', 0); + loader.transition().duration(10).style('opacity', 0) } } // show the details of the candle on mouseover, touch function showDetails() { - var z = wrap.style('zoom') || 1; - var x = d3.mouse(this)[0] / z; - var tx = Math.max(0, Math.min(options.width + options.margin.left, x)); - var i = d3.bisect(lineData.map(function(d) { - return d.startTime; - }), xScale.invert(tx - options.margin.left)); - var d = lineData[i]; - var open; - var high; - var low; - var close; - var volume; - var vwap; - if (d) { - if (rippleAmount) { - open = rippleAmount(d.open, counter.currency); - high = rippleAmount(d.high, counter.currency); - low = rippleAmount(d.low, counter.currency); - close = rippleAmount(d.close, counter.currency); - vwap = rippleAmount(d.vwap, counter.currency); - volume = rippleAmount(d.baseVolume, base.currency); + var z = wrap.style('zoom') || 1 - } else { - open = d.open.toFixed(4); - high = d.high.toFixed(4); - low = d.low.toFixed(4); - close = d.close.toFixed(4); - vwap = d.vwap.toFixed(4); - volume = d.baseVolume.toFixed(2); - } + if (z === 'normal') { + z = 1 + } else if (/%/.test(z)) { + z = parseFloat(z) / 100 + } - var baseCurrency = base.currency; - var chartDetails = div.select('.chartDetails'); + var x = d3.mouse(this)[0] / z + var tx = Math.max(0, Math.min(options.width + options.margin.left, x)) + + var i = d3.bisect(lineData.map(function(d) { + return d.startTime + }), xScale.invert(tx - options.margin.left)) + var d = lineData[i] + var open + var high + var low + var close + var volume + var vwap + + if (d) { + open = d.open.toPrecision(5) + high = d.high.toPrecision(5) + low = d.low.toPrecision(5) + close = d.close.toPrecision(5) + vwap = d.vwap.toPrecision(5) + volume = d.baseVolume.toFixed(2) + + var baseCurrency = base.currency + var chartDetails = div.select('.chartDetails') chartDetails.html('' + parseDate(d.startTime.local(), chartInterval) + 'O:' + open + '' + @@ -160,37 +159,37 @@ function PriceChart(options) { 'VWAP:' + vwap + '' + 'Vol:' + volume + ' ' + baseCurrency + '') - .style('opacity', 1); + .style('opacity', 1) hover.transition().duration(50) - .attr('transform', 'translate(' + xScale(d.startTime) + ')'); + .attr('transform', 'translate(' + xScale(d.startTime) + ')') focus.transition().duration(50) .attr('transform', 'translate(' + xScale(d.startTime) + ',' + - priceScale(d.close) + ')'); + priceScale(d.close) + ')') horizontal.transition().duration(50) .attr('x1', xScale(d.startTime)) .attr('x2', options.width) .attr('y1', priceScale(d.close)) - .attr('y2', priceScale(d.close)); + .attr('y2', priceScale(d.close)) - hover.style('opacity', 1); - horizontal.style('opacity', 1); - focus.style('opacity', 1); + hover.style('opacity', 1) + horizontal.style('opacity', 1) + focus.style('opacity', 1) } } // this can be a function that will return whenever the state changes, // such as when we start loading historical data, or stop - self.onStateChange = null; + self.onStateChange = null - type = options.type ? options.type : 'line'; // default to line + type = options.type ? options.type : 'line' // default to line wrap = options.id ? - d3.select('#' + options.id) : d3.select('body').append('div'); - h = parseInt(wrap.style('height'), 10) || 0; - w = parseInt(wrap.style('width'), 10) || 0; - div = wrap.append('div').attr('class', 'priceChart'); - wrap.classed('priceChartWrap'); + d3.select('#' + options.id) : d3.select('body').append('div') + h = parseInt(wrap.style('height'), 10) || 0 + w = parseInt(wrap.style('width'), 10) || 0 + div = wrap.append('div').attr('class', 'priceChart') + wrap.classed('priceChartWrap') if (!options.margin) { options.margin = { @@ -198,39 +197,39 @@ function PriceChart(options) { right: 60, bottom: 20, left: 60 - }; + } } if (!options.width) { - options.width = w - options.margin.left - options.margin.right; + options.width = w - options.margin.left - options.margin.right } if (options.height) { - options.height -= options.margin.top - options.margin.bottom; + options.height -= options.margin.top - options.margin.bottom } else if (h) { options.height = h - options.margin.top - - options.margin.bottom; + options.margin.bottom } else { options.height = window.innerHeight - options.margin.top - - options.margin.bottom; + options.margin.bottom } if (options.height < 150) { - options.height = 150; + options.height = 150 } if (options.width < 0) { - options.width = 0; + options.width = 0 } // draw the chart at the beginning and whenever it is resized (if resizable) function drawChart() { - div.html(''); - svg = div.selectAll('svg').data([0]); + div.html('') + svg = div.selectAll('svg').data([0]) svg.enter().append('svg') .attr('width', options.width + @@ -238,50 +237,50 @@ function PriceChart(options) { options.margin.right) .attr('height', options.height + options.margin.top + - options.margin.bottom); + options.margin.bottom) svg.append('defs').append('clipPath') .attr('id', 'clip') - .append('rect'); + .append('rect') svg.select('rect') .attr('width', options.width) - .attr('height', options.height); + .attr('height', options.height) gEnter = svg.append('g') .attr('transform', 'translate(' + options.margin.left + ',' + - options.margin.top + ')'); + options.margin.top + ')') gEnter.append('rect') .attr('class', 'background') .attr('width', options.width) - .attr('height', options.height); + .attr('height', options.height) - gEnter.append('g').attr('class', 'x axis'); + gEnter.append('g').attr('class', 'x axis') gEnter.append('g').attr('class', 'volume axis') .append('text').text('Volume') .attr('class', 'title') .attr('transform', 'rotate(-90)') - .attr('y', 15).attr('x', -110); + .attr('y', 15).attr('x', -110) gEnter.append('g').attr('class', 'price axis') .attr('transform', 'translate(' + options.width + ', 0)') .append('text').text('Price') .attr('class', 'title') .attr('transform', 'rotate(-90)') - .attr('y', -10).attr('x', -100); + .attr('y', -10).attr('x', -100) - gEnter.append('path').attr('class', 'line'); + gEnter.append('path').attr('class', 'line') gEnter.append('g') .attr('class', 'volumeBars') - .attr('clip-path', 'url(#clip)'); + .attr('clip-path', 'url(#clip)') gEnter.append('g') .attr('class', 'candlesticks') - .attr('clip-path', 'url(#clip)'); + .attr('clip-path', 'url(#clip)') // gradient for volume bars gradient = svg.append('svg:defs') @@ -291,86 +290,86 @@ function PriceChart(options) { .attr('y1', '0%') .attr('x2', '0%') .attr('y2', '100%') - .attr('spreadMethod', 'pad'); + .attr('spreadMethod', 'pad') gradient.append('svg:stop') .attr('offset', '0%') .attr('stop-color', '#ccc') - .attr('stop-opacity', 0.5); + .attr('stop-opacity', 0.5) gradient.append('svg:stop') .attr('offset', '100%') .attr('stop-color', '#ddd') - .attr('stop-opacity', 0.5); + .attr('stop-opacity', 0.5) hover = gEnter.append('line') .attr('class', 'hover') - .attr('y2', options.height); + .attr('y2', options.height) horizontal = gEnter.append('line') - .attr('class', 'hover'); + .attr('class', 'hover') focus = gEnter.append('circle') .attr('class', 'focus dark') - .attr('r', 3); + .attr('r', 3) status = div.append('h4') - .attr('class', 'status'); + .attr('class', 'status') details = div.append('div') .attr('class', 'chartDetails') .style('left', options.margin.left + 'px') .style('right', options.margin.right + 'px') .style('top', (options.margin.top - 1) + 'px') - .style('opacity', 0); + .style('opacity', 0) loader = div.append('img') .attr('class', 'loader') .attr('src', LOADER_PNG) - .style('opacity', 0); + .style('opacity', 0) if (isLoading) { - svg.style('opacity', 0.5); - loader.style('opacity', 1); + svg.style('opacity', 0.5) + loader.style('opacity', 1) } } // draw data function drawData(update) { if (!isLoading && (!lineData || !lineData.length)) { - setStatus('No Data for this Period'); + setStatus('No Data for this Period') } else { - setStatus(''); + setStatus('') } - var duration = update ? 0 : 250; + var duration = update ? 0 : 250 // aiming for around 100-200 here var num = (moment(endTime).unix() - moment(startTime).unix()) / - intervalSeconds; - var candleWidth = options.width / (num * 1.5); + intervalSeconds + var candleWidth = options.width / (num * 1.5) if (candleWidth < 3) { - candleWidth = 1; + candleWidth = 1 } else if (candleWidth < 4) { - candleWidth = 2; + candleWidth = 2 } - var baseCurrency = base.currency; - var counterCurrency = counter.currency; + var baseCurrency = base.currency + var counterCurrency = counter.currency gEnter.select('.axis.price').select('text') - .text('Price (' + counterCurrency + ')'); + .text('Price (' + counterCurrency + ')') gEnter.select('.axis.volume').select('text') - .text('Volume (' + baseCurrency + ')'); + .text('Volume (' + baseCurrency + ')') svg.datum(lineData, function(d) { - return d.startTime; + return d.startTime }) .on('mousemove', showDetails) .on('touchmove', showDetails) .on('touchstart', showDetails) - .on('touchend', showDetails); + .on('touchend', showDetails) // Update the x-scale. xScale @@ -379,73 +378,73 @@ function PriceChart(options) { moment.utc(endTime) .add(num / 35 * intervalSeconds, 'seconds') ]) - .range([0, options.width]); + .range([0, options.width]) // Update the volume scale. volumeScale .domain([ 0, d3.max(lineData, function(d) { - return d.baseVolume; + return d.baseVolume }) * 2 ]) - .range([options.height, 0]); + .range([options.height, 0]) if (type === 'line') { - gEnter.select('.line').style('opacity', 1); - gEnter.select('.candlesticks').style('opacity', 0); + gEnter.select('.line').style('opacity', 1) + gEnter.select('.candlesticks').style('opacity', 0) // update the price scale priceScale .domain([ d3.min(lineData, function(d) { - return Math.min(d.close); + return Math.min(d.close) }) * 0.975, d3.max(lineData, function(d) { - return Math.max(d.close); + return Math.max(d.close) }) * 1.025 ]) - .range([options.height - 20, 0]); + .range([options.height - 20, 0]) } else { - gEnter.select('.line').style('opacity', 0); - gEnter.select('.candlesticks').style('opacity', 1); + gEnter.select('.line').style('opacity', 0) + gEnter.select('.candlesticks').style('opacity', 1) priceScale .domain([ d3.min(lineData, function(d) { - return Math.min(d.open, d.close, d.high, d.low); + return Math.min(d.open, d.close, d.high, d.low) }) * 0.975, d3.max(lineData, function(d) { - return Math.max(d.open, d.close, d.high, d.low); + return Math.max(d.open, d.close, d.high, d.low) }) * 1.025 ]) - .range([options.height - 20, 0]); + .range([options.height - 20, 0]) } var line = d3.svg.line() .x(function(d) { - return xScale(d.startTime); + return xScale(d.startTime) }) .y(function(d) { - return priceScale(d.close); - }); + return priceScale(d.close) + }) // add the price line gEnter.select('.line') .datum(lineData, function(d) { - return d.startTime; + return d.startTime }) .transition().duration(duration) - .attr('d', line); + .attr('d', line) // add the candlesticks. var candle = gEnter.select('.candlesticks').selectAll('g') .data(lineData, function(d) { - return d.startTime; - }); + return d.startTime + }) /* * Candlestick rules: @@ -457,183 +456,183 @@ function PriceChart(options) { var candleEnter = candle.enter().append('g') .attr('transform', function(d) { - return 'translate(' + xScale(d.startTime) + ')'; - }); + return 'translate(' + xScale(d.startTime) + ')' + }) - candleEnter.append('line').attr('class', 'extent'); - candleEnter.append('line').attr('class', 'high'); - candleEnter.append('line').attr('class', 'low'); - candleEnter.append('rect'); + candleEnter.append('line').attr('class', 'extent') + candleEnter.append('line').attr('class', 'high') + candleEnter.append('line').attr('class', 'low') + candleEnter.append('rect') var candleUpdate = candle.classed('up', function(d, i) { if (i > 0) { - var prev = lineData[i - 1]; - return prev.close <= d.close; + var prev = lineData[i - 1] + return prev.close <= d.close } - return d.open <= d.close; // just for the first, accurate most of the time + return d.open <= d.close // just for the first, accurate most of the time }) .classed('filled', function(d) { - return d.close <= d.open; + return d.close <= d.open }) .transition().duration(duration) .attr('transform', function(d) { - return 'translate(' + xScale(d.startTime) + ')'; - }); + return 'translate(' + xScale(d.startTime) + ')' + }) candleUpdate.select('.extent') .attr('y1', function(d) { - return priceScale(d.low); + return priceScale(d.low) }) .attr('y2', function(d) { - return priceScale(d.high); - }); + return priceScale(d.high) + }) candleUpdate.select('rect') .attr('x', -candleWidth / 2) .attr('width', candleWidth) .attr('y', function(d) { - return priceScale(Math.max(d.open, d.close)); + return priceScale(Math.max(d.open, d.close)) }) .attr('height', function(d) { - return Math.abs(priceScale(d.open) - priceScale(d.close)) + 0.5; - }); + return Math.abs(priceScale(d.open) - priceScale(d.close)) + 0.5 + }) candleUpdate.select('.high') .attr('x1', -candleWidth / 4) .attr('x2', candleWidth / 4) .attr('y1', function(d) { - return priceScale(d.high); + return priceScale(d.high) }) .attr('y2', function(d) { - return priceScale(d.high); - }); + return priceScale(d.high) + }) candleUpdate.select('.low') .attr('x1', -candleWidth / 4) .attr('x2', candleWidth / 4) .attr('y1', function(d) { - return priceScale(d.low); + return priceScale(d.low) }) .attr('y2', function(d) { - return priceScale(d.low); - }); + return priceScale(d.low) + }) d3.transition(candle.exit()) .attr('transform', function(d) { - return 'translate(' + xScale(d.startTime) + ')'; + return 'translate(' + xScale(d.startTime) + ')' }) - .style('opacity', 1e-6).remove(); + .style('opacity', 1e-6).remove() // add the volume bars var bars = gEnter.select('.volumeBars') .selectAll('rect') .data(lineData, function(d) { - return d.startTime; - }); + return d.startTime + }) - bars.enter().append('rect'); + bars.enter().append('rect') bars.data(lineData, function(d) { - return d.startTime; + return d.startTime }) .transition().duration(duration) .attr('x', function(d) { - return xScale(d.startTime) - candleWidth / 3; + return xScale(d.startTime) - candleWidth / 3 }) .attr('y', function(d) { - return volumeScale(d.baseVolume); + return volumeScale(d.baseVolume) }) .attr('width', candleWidth / 1.2) .attr('height', function(d) { - return options.height - volumeScale(d.baseVolume); + return options.height - volumeScale(d.baseVolume) }) - .style('fill', 'url(#gradient)'); + .style('fill', 'url(#gradient)') - bars.exit().remove(); + bars.exit().remove() // Update the x-axis. gEnter.select('.x.axis') .attr('transform', 'translate(0,' + volumeScale.range()[0] + ')') .transition().duration(duration) - .call(xAxis); + .call(xAxis) // Update the y-axis. gEnter.select('.price.axis') .attr('transform', 'translate(' + xScale.range()[1] + ', 0)') .transition().duration(duration) - .call(priceAxis); + .call(priceAxis) // Update the left axis. gEnter.select('.volume.axis') .transition().duration(duration) - .call(volumeAxis); + .call(volumeAxis) // hide the loader, show the chart if (!isLoading) { - svg.transition().duration(duration).style('opacity', 1); - loader.transition().duration(duration).style('opacity', 0); + svg.transition().duration(duration).style('opacity', 1) + loader.transition().duration(duration).style('opacity', 0) } } // add new data from the live feed to the chart function liveUpdate(data) { - var first = lineData.length ? lineData[0] : null; - var last = lineData.length ? lineData[lineData.length - 1] : null; - var candle = data; + var first = lineData.length ? lineData[0] : null + var last = lineData.length ? lineData[lineData.length - 1] : null + var candle = data - candle.startTime = moment.utc(candle.startTime); - candle.live = true; + candle.startTime = moment.utc(candle.startTime) + candle.live = true - // console.log(candle); - // console.log(last.startTime.format(), candle.startTime.format()); + // console.log(candle) + // console.log(last.startTime.format(), candle.startTime.format()) if (last && last.startTime.unix() === candle.startTime.unix()) { - // console.log('update'); + // console.log('update') - lineData[lineData.length - 1] = candle; + lineData[lineData.length - 1] = candle } else { - // console.log('append'); + // console.log('append') // new candle, only add it if something happened if (candle.baseVolume) { - lineData.push(candle); // append the candle + lineData.push(candle) // append the candle } // adjust the range - startTime.add(intervalSeconds, 'seconds'); - endTime.add(intervalSeconds, 'seconds'); + startTime.add(intervalSeconds, 'seconds') + endTime.add(intervalSeconds, 'seconds') // remove the first candle if it is before the start range if (first && first.startTime.unix() < startTime.unix()) { - lineData.shift(); + lineData.shift() } } // redraw the chart if (lineData.length) { - drawData(); + drawData() } } // function called whenever the window is resized (if resizable) function resizeChart() { - var oldWidth = options.width; - var oldHeight = options.height; + var oldWidth = options.width + var oldHeight = options.height - var width = parseInt(wrap.style('width'), 10); - var height = parseInt(wrap.style('height'), 10); + var width = parseInt(wrap.style('width'), 10) + var height = parseInt(wrap.style('height'), 10) if (!width || !height) { - return; + return } - options.width = width - options.margin.left - options.margin.right; - options.height = height - options.margin.top - options.margin.bottom; + options.width = width - options.margin.left - options.margin.right + options.height = height - options.margin.top - options.margin.bottom if (options.height < 150) { - options.height = 150; + options.height = 150 } if (oldWidth !== options.width || @@ -644,57 +643,57 @@ function PriceChart(options) { options.margin.right) .attr('height', options.height + options.margin.top + - options.margin.bottom); + options.margin.bottom) svg.select('rect') .attr('width', options.width) - .attr('height', options.height); + .attr('height', options.height) svg.select('rect.background') .attr('width', options.width) - .attr('height', options.height); + .attr('height', options.height) if (base && counter) { - drawData(true); + drawData(true) } } } // apply rules to get the start times to line up nicely function getAlignedCandle(time) { - var aligned; + var aligned - time.subtract(time.milliseconds(), 'milliseconds'); + time.subtract(time.milliseconds(), 'milliseconds') if (chartInterval === 'se') { - aligned = time.subtract(time.seconds() % multiple, 'seconds'); + aligned = time.subtract(time.seconds() % multiple, 'seconds') } else if (chartInterval === 'mi') { aligned = time.subtract({ seconds: time.seconds(), minutes: time.minutes() % multiple - }); + }) } else if (chartInterval === 'ho') { aligned = time.subtract({ seconds: time.seconds(), minutes: time.minutes(), hours: time.hours() % multiple - }); + }) } else if (chartInterval === 'da') { - var days; - var diff; + var days + var diff if (multiple === 1) { - days = 0; + days = 0 } else { - diff = time.diff(moment.utc([2013, 0, 1]), 'hours') / 24; + diff = time.diff(moment.utc([2013, 0, 1]), 'hours') / 24 if (diff < 0) { - days = multiple - (0 - Math.floor(diff)) % multiple; + days = multiple - (0 - Math.floor(diff)) % multiple } else { - days = Math.floor(diff) % multiple; + days = Math.floor(diff) % multiple } } @@ -703,7 +702,7 @@ function PriceChart(options) { minutes: time.minutes(), hours: time.hours(), days: days - }); + }) } else if (chartInterval === 'we') { aligned = time.subtract({ @@ -712,7 +711,7 @@ function PriceChart(options) { hours: time.hours(), days: time.day(), weeks: time.isoWeek() % multiple - }); + }) } else if (chartInterval === 'mo') { aligned = time.subtract({ @@ -721,10 +720,10 @@ function PriceChart(options) { hours: time.hours(), days: time.date() - 1, months: time.months() % multiple - }); + }) } - return aligned; + return aligned } @@ -742,31 +741,31 @@ function PriceChart(options) { vwap: 0.0, openTime: Infinity, closeTime: 0 - }; + } - var interval; + var interval switch (chartInterval) { case ('se'): - interval = 'second'; - break; + interval = 'second' + break case ('mi'): - interval = 'minute'; - break; + interval = 'minute' + break case ('ho'): - interval = 'hour'; - break; + interval = 'hour' + break case ('da'): - interval = 'day'; - break; + interval = 'day' + break case ('we'): - interval = 'week'; - break; + interval = 'week' + break case ('mo'): - interval = 'month'; - break; + interval = 'month' + break default: - interval = chartInterval; + interval = chartInterval } var viewOptions = { @@ -775,136 +774,136 @@ function PriceChart(options) { timeIncrement: interval, timeMultiple: multiple, incompleteApiRow: candle - }; + } if (liveFeed) { - liveFeed.updateViewOpts(viewOptions); + liveFeed.updateViewOpts(viewOptions) } else { - liveFeed = new OffersExercisedListener(viewOptions, liveUpdate); + liveFeed = new OffersExercisedListener(viewOptions, liveUpdate) } } if (options.resize) { - addResizeListener(wrap.node(), resizeChart); + addResizeListener(wrap.node(), resizeChart) } else { var padding = parseInt(details.style('padding-left'), 10) + - parseInt(details.style('padding-right'), 10); + parseInt(details.style('padding-right'), 10) details.style('width', (options.width - padding) + 'px') - .style('right', 'auto'); + .style('right', 'auto') div.style('width', (options.width + options.margin.left + options.margin.right) + - 'px'); + 'px') div.style('height', ((options.height || staticHeight) + options.margin.top + options.margin.bottom) + - 'px'); + 'px') } - drawChart(); // have to do this here so that the details div is drawn + drawChart() // have to do this here so that the details div is drawn this.resizeChart = function() { - resizeChart(); - }; + resizeChart() + } // fade to throber when loading historical data this.fadeOut = function() { - div.selectAll('svg').transition().duration(100).style('opacity', 0.5); - svg.on('mousemove', null).on('touchmove', null); - details.style('opacity', 0); - status.style('opacity', 0); - div.selectAll('.hover').style('opacity', 0); - div.selectAll('.focus').style('opacity', 0); - loader.transition().duration(100).style('opacity', 1); - }; + div.selectAll('svg').transition().duration(100).style('opacity', 0.5) + svg.on('mousemove', null).on('touchmove', null) + details.style('opacity', 0) + status.style('opacity', 0) + div.selectAll('.hover').style('opacity', 0) + div.selectAll('.focus').style('opacity', 0) + loader.transition().duration(100).style('opacity', 1) + } // set to line or candlestick this.setType = function(newType) { - type = newType; + type = newType if (lineData.length) { - drawData(); // dont draw unless its been loaded + drawData() // dont draw unless its been loaded } - }; + } // function for getting the raw data for csv output or whatever this.getRawData = function() { - return lineData; - }; + return lineData + } // load historical from API this.load = function(b, c, d) { if (!b) { - setStatus('Base currency is required.'); - return; + setStatus('Base currency is required.') + return } if (!c) { - setStatus('Counter currency is required.'); - return; + setStatus('Counter currency is required.') + return } if (!d || !d.interval) { - setStatus('Interval is required.'); - return; + setStatus('Interval is required.') + return } - chartInterval = d.interval.slice(0, 2); + chartInterval = d.interval.slice(0, 2) switch (chartInterval) { case ('se'): - intervalSeconds = 1; - break; + intervalSeconds = 1 + break case ('mi'): - intervalSeconds = 60; - break; + intervalSeconds = 60 + break case ('ho'): - intervalSeconds = 60 * 60; - break; + intervalSeconds = 60 * 60 + break case ('da'): - intervalSeconds = 60 * 60 * 24; - break; + intervalSeconds = 60 * 60 * 24 + break case ('we'): - intervalSeconds = 60 * 60 * 24 * 7; - break; + intervalSeconds = 60 * 60 * 24 * 7 + break case ('mo'): - intervalSeconds = 60 * 60 * 24 * 30.5; // approx - break; + intervalSeconds = 60 * 60 * 24 * 30.5 // approx + break default: - setStatus('Invalid Interval'); + setStatus('Invalid Interval') } if (self.onStateChange) { - self.onStateChange('loading'); + self.onStateChange('loading') } - self.fadeOut(); - base = b; - counter = c; - multiple = d.multiple || 1; - lineData = []; - isLoading = true; + self.fadeOut() + base = b + counter = c + multiple = d.multiple || 1 + lineData = [] + isLoading = true - intervalSeconds *= multiple; - lastCandle = getAlignedCandle(moment.utc(d.end)); - endTime = moment.utc(lastCandle).add(intervalSeconds, 'seconds'); + intervalSeconds *= multiple + lastCandle = getAlignedCandle(moment.utc(d.end)) + endTime = moment.utc(lastCandle).add(intervalSeconds, 'seconds') startTime = d.start ? - getAlignedCandle(moment.utc(d.start)) : moment.utc(d.offset(endTime)); + getAlignedCandle(moment.utc(d.start)) : moment.utc(d.offset(endTime)) if (liveFeed) { - liveFeed.stopListener(); + liveFeed.stopListener() } if (options.live && d.live) { - setLiveFeed(); + setLiveFeed() } if (self.request) { - self.request.abort(); + self.request.abort() } self.request = apiHandler.offersExercised({ @@ -921,56 +920,56 @@ function PriceChart(options) { // the first live data with the last historic candle if (lineData.length && data.length) { - var first = lineData.shift(); - var candle = data[data.length - 1]; - var volume = candle.baseVolume + first.baseVolume; + var first = lineData.shift() + var candle = data[data.length - 1] + var volume = candle.baseVolume + first.baseVolume // candle.open will be from historic if (candle.high > first.high) { - candle.high = first.high; + candle.high = first.high } if (candle.low < first.low) { - candle.low = first.low; + candle.low = first.low } candle.vwap = (candle.vwap * candle.baseVolume + - first.vwap * first.baseVolume) / volume; - candle.baseVolume = volume; - candle.close = first.close; - candle.closeTime = first.closeTime; - data[data.length - 1] = candle; + first.vwap * first.baseVolume) / volume + candle.baseVolume = volume + candle.close = first.close + candle.closeTime = first.closeTime + data[data.length - 1] = candle } // merge the last candle with the updater candle - if // its not the same time interval it will be discarded if (options.live && d.live) { - liveFeed.resetStored(data[data.length - 1], true); + liveFeed.resetStored(data[data.length - 1], true) } - lineData = data.concat(lineData); - isLoading = false; + lineData = data.concat(lineData) + isLoading = false if (self.onStateChange) { - self.onStateChange('loaded'); + self.onStateChange('loaded') } - drawData(); + drawData() }, function(error) { if (self.onStateChange) { - self.onStateChange('error'); + self.onStateChange('error') } - isLoading = false; - console.log(error); - setStatus(error.text ? error.text : 'Unable to load data'); - }); - }; + isLoading = false + console.log(error) + setStatus(error.text ? error.text : 'Unable to load data') + }) + } // suspend the live feed this.suspend = function() { if (liveFeed) { - liveFeed.stopListener(); + liveFeed.stopListener() } - }; + } } diff --git a/src/common/totalHistory.js b/src/common/totalHistory.js index 02e44f1..0ba33e5 100644 --- a/src/common/totalHistory.js +++ b/src/common/totalHistory.js @@ -1,19 +1,41 @@ -var TotalHistory = function (options) { - - var self = this; - var gateways = options.gateways; - var api = new ApiHandler(options.url); - var el = d3.select('#' + options.id); - var color = d3.scale.category10(); - var chart = new StackedChart({ - div: el.append('div').attr('class', 'chart'), - title: 'Total Volume', - resize: options.resize, - formatValue: formatValue, - formatLabel: formatLabel, - dateFormat: 'MMM D, YYYY [(UTC)]', - color: color - }); +/* eslint + no-use-before-define: 1, + no-unused-vars: ["warn", { "args": "after-used" }], + prefer-spread:1, + no-loop-func: 0 +*/ +/* globals StackedChart */ +'use strict' + +function getIEVersion() { + var sAgent = window.navigator.userAgent + var Idx = sAgent.indexOf('MSIE') + + // If IE, return version number. + if (Idx > 0) { + return parseInt(sAgent.substring(Idx + 5, sAgent.indexOf('.', Idx)), 10) + + // If IE 11 then look for Updated user agent string. + } else if (navigator.userAgent.match(/Trident\/7\./)) { + return 11 + } + + return 0 // It is not IE +} + +/** + * TotalHistory + */ + +function TotalHistory(options) { + + var self = this + var gateways = options.gateways + var api = new ApiHandler(options.url) + var el = d3.select('#' + options.id) + var color = d3.scale.category10() + var customFrom + var customTo var issuers = { USD: 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B', @@ -22,550 +44,469 @@ var TotalHistory = function (options) { EUR: 'rhub8VRN55s94qWKDv6jmDy1pUykJzF3wq', JPY: 'r94s8px6kSw1uZ1MV98dhSRTvc6VMPoPcN', XRP: '' - }; - - var filters = []; - var cache; - var crumbs = d3.select('#breadcrumb .crumbs'); - var currencySelect = d3.select('#currency select'); - var ranges = d3.selectAll('#ranges a'); - var intervals = d3.selectAll('#intervals a'); - var download = d3.select('#csv'); - - var formatNumber = d3.format(',.0f'); - var range = '1m'; - var interval = 'day'; - var currency = 'USD'; - var issuer = issuers[currency]; - - var legend = el.append('div') - .attr('class', 'legend'); - - currencySelect.on('change', changeCurrency); - currencySelect.property('value', currency); - - ranges.classed('clicked', function() { - return d3.select(this).text() === range; - }).on('click', changeRange); - - intervals.classed('clicked', function() { - return d3.select(this).text() === interval; - }).on('click', changeInterval); - - intervals.classed('no-click', function() { - return d3.select(this).text() === 'month'; - }); - - $('#datepicker_from').val(moment().subtract(1, 'month') - .subtract(1, 'day').format('MM/DD/YYYY')); - $('#datepicker_to').val(moment().format('MM/DD/YYYY')); - - var customTo = $('#datepicker_to').datepicker({ - maxDate: '+0d', - minDate: '', - onSelect: loadCustomRange - }); - - var customFrom = $('#datepicker_from').datepicker({ - maxDate: '-2d', - onSelect: loadCustomRange - }); - - function loadCustomRange(dateText) { - var start = moment(customFrom.val()); - var end = moment(customTo.val()); - var diff = end.diff(start, 'days'); - var noclick; - - if (diff < 90) { - noclick = 'month'; - } else if (diff > 365) { - noclick = 'day' - } - - intervals.classed('no-click', function() { - return d3.select(this).text() === noclick; - }); - - if (noclick === interval && - interval === 'month') { - changeInterval('day'); - - } else if (noclick === interval && - interval === 'day') { - changeInterval('week'); - - } else { - self.load(); - } } - download.on('click', function() { - var csv = toCSV(cache.current.sets); - var name = crumbs.select('li:last-child').text(); - - if (!!Modernizr.prefixed('requestFileSystem', window)) { - var blob = new Blob([csv], {'type':'application/octet-stream'}); - this.href = window.URL.createObjectURL(blob); - } else { - this.href = 'data:text/csv;charset=utf-8,' + escape(csv); - } - - this.download = name + '_' + interval + '_historical.csv'; - this.target = '_blank'; - return true; - }); + var filters = [] + var cache + var crumbs = d3.select('#breadcrumb .crumbs') + var currencySelect = d3.select('#currency select') + var ranges = d3.selectAll('#ranges a') + var intervals = d3.selectAll('#intervals a') + var download = d3.select('#csv') + + var formatNumber = d3.format(',.0f') + var range = '1m' + var interval = 'day' + var currency = 'USD' + var issuer = issuers[currency] + var legend + var chart function toCSV(data) { - var str =''; - var line = ['Date']; + var str = '' + var line = ['Date'] - line.push.apply(line, Object.keys(data)); - str += line.join(',') + '\r\n'; - var key = line[1]; + line.push.apply(line, Object.keys(data)) + str += line.join(',') + '\r\n' + var key = line[1] - var byDate = {}; + var byDate = {} for (key in data) { data[key].forEach(function(d) { - var date = moment(d.date).utc().format('YYYY-MM-DD'); + var date = moment(d.date).utc().format('YYYY-MM-DD') if (!byDate[date]) { - byDate[date] = [date]; + byDate[date] = [date] } - byDate[date].push(d.y); - }); + byDate[date].push(d.y) + }) } for (key in byDate) { - str += byDate[key].join(',') + '\r\n'; + str += byDate[key].join(',') + '\r\n' } - return str; + return str + } + + function changeInterval(newInterval) { + var a = d3.select(this) + + if (!newInterval && a.classed('no-click')) { + return + } + + interval = newInterval ? + newInterval : a.text() + + intervals.classed('clicked', function() { + return d3.select(this).text() === interval + }) + self.load() } function changeRange() { - range = d3.select(this).text(); - intervals.classed('no-click', false); + range = d3.select(this).text() + intervals.classed('no-click', false) ranges.classed('clicked', function() { - return d3.select(this).text() === range; - }); + return d3.select(this).text() === range + }) if (range === 'custom') { d3.selectAll('#ranges .calendar') .style('display', function() { return d3.select(this).style('display') === 'none' ? - 'inline-block' : 'none'; - }); + 'inline-block' : 'none' + }) - return; + return } else if (range === 'max') { intervals.classed('no-click', function() { - return d3.select(this).text() === 'day'; - }); + return d3.select(this).text() === 'day' + }) if (interval === 'day') { - changeInterval('week'); - return; + changeInterval('week') + return } } else if (range === '1m') { intervals.classed('no-click', function() { - return d3.select(this).text() === 'month'; - }); + return d3.select(this).text() === 'month' + }) if (interval === 'month') { - changeInterval('day'); - return; + changeInterval('day') + return } } - self.load(); - } - - function changeInterval(newInterval) { - var a = d3.select(this); - - if (!newInterval && a.classed('no-click')) { - return; - } - - interval = newInterval ? - newInterval : a.text(); - - intervals.classed('clicked', function() { - return d3.select(this).text() === interval; - }); - self.load(); + self.load() } function changeCurrency() { - currency = d3.select(this).node().value; - issuer = issuers[currency]; - self.load(); + currency = d3.select(this).node().value + issuer = issuers[currency] + self.load() } function formatValue(d) { - return (currency === 'USD' ? '$' : '') + formatNumber(d); + return (currency === 'USD' ? '$' : '') + formatNumber(d) } function formatLabel(d) { - var parts = d.split('.'); + var parts = d.split('.') if (parts.length === 4) { - var bi = gateways.getName(parts[1]) || parts[1]; - var ci = gateways.getName(parts[3]) || parts[3]; + var bi = gateways.getName(parts[1]) || parts[1] + var ci = gateways.getName(parts[3]) || parts[3] return parts[0] + ' ' + bi + ' / ' + - parts[2] + ' ' + ci + ''; + parts[2] + ' ' + ci + '' } else if (parts.length === 2) { - var i = gateways.getName(parts[1]) || parts[1]; - return parts[0] + ' ' + i + ''; + var i = gateways.getName(parts[1]) || parts[1] + return parts[0] + ' ' + i + '' } - return d; + return d } - /** - * load - */ - - self.load = function() { - filters = []; - chart.loading = true; - chart.fadeOut(); - showCrumbs(); + function loadCustomRange() { + var start = moment(customFrom.val()) + var end = moment(customTo.val()) + var diff = end.diff(start, 'days') + var noclick - function loadData(metric) { - return new Promise(function(resolve, reject) { - - var end = moment.utc(); - var start; + if (diff < 90) { + noclick = 'month' + } else if (diff > 365) { + noclick = 'day' + } - if (range === 'max') { - start = moment.utc('2013-01-01'); - } else if (range === 'custom') { - start = moment.utc(customFrom.val()); - end = moment.utc(customTo.val()); - } else if (range[1] === 'y') { - start = moment.utc().subtract(1, 'years'); - } else { - start = moment.utc().subtract(range[0], 'months'); - } + intervals.classed('no-click', function() { + return d3.select(this).text() === noclick + }) - api[metric]({ - currency: currency, - issuer: issuer, - start: start, - end: end, - interval: interval - },function(err, data) { + if (noclick === interval && + interval === 'month') { + changeInterval('day') - if (err) { - reject(err); + } else if (noclick === interval && + interval === 'day') { + changeInterval('week') - } else { - resolve(data.rows); - } - }); - }); + } else { + self.load() } - - Promise.all([ - loadData('getExchangeVolume'), - loadData('getPaymentVolume') - ]) - .then(cacheResult) - .then(graphData) - .then(drawLegend) - .catch(function(e) { - console.log(e); - console.trace(); - chart.loading = false; - }); } function cacheResult(data) { - chart.loading = false; + chart.loading = false cache = { original: data, byDate: {} - }; + } data[0].forEach(function(d) { - var date = moment(d.start_time).format(); + var date = moment(d.start_time).format() if (!cache.byDate[date]) { cache.byDate[date] = { date: date - }; + } } - cache.byDate[date]['Exchange Volume'] = d; - }); + cache.byDate[date]['Exchange Volume'] = d + }) data[1].forEach(function(d) { - var date = moment(d.start_time).format(); + var date = moment(d.start_time).format() if (!cache.byDate[date]) { cache.byDate[date] = { date: date - }; - } - - cache.byDate[date]['Payment Volume'] = d; - }); - } - - function graphData() { - var date; - var data = {}; - var keys = {}; - var dates = {}; - var key; - - if (!filters.length) { - data = { - 'Exchange Volume': [], - 'Payment Volume': [] - }; - - for (date in cache.byDate) { - data['Exchange Volume'].push({ - date: moment(date), - y: cache.byDate[date]['Exchange Volume'] ? - cache.byDate[date]['Exchange Volume'].total : 0 - }); - } - - for (date in cache.byDate) { - data['Payment Volume'].push({ - date: moment(date), - y: cache.byDate[date]['Payment Volume'] ? - cache.byDate[date]['Payment Volume'].total : 0 - }); - } - - data = { - sets: data, - keys: { - 'Exchange Volume': { - label: 'Exchange Volume' - }, - 'Payment Volume': { - label: 'Payment Volume' - } } - }; - - } else if (filters[1] && filters[0] === 'Exchange Volume') { - data = filterByMarket() - - } else if (filters[1] && filters[0] === 'Payment Volume') { - data = filterByIssuer(); - } else { - data = filterByCurrency(); - } - - - for (key in data.sets) { - data.sets[key].sort(function(a,b) { - return a.date.diff(b.date); - }); - } + } - cache.current = data; - chart.redraw(data.sets); - return data; + cache.byDate[date]['Payment Volume'] = d + }) } function filterByIssuer() { - var data = {}; - var keys = {}; - var source; - var date; - var key; + var data = {} + var keys = {} + var list + var source + var date + var key for (date in cache.byDate) { - source = cache.byDate[date]['Payment Volume']; + source = cache.byDate[date]['Payment Volume'] + + if (!source) { + continue + } source.components.forEach(function(c) { if (c.currency !== filters[1]) { - return; + return } - var currency = c.currency + '.' + (c.issuer || ''); - keys[currency] = { - label: currency, + key = c.currency + '.' + (c.issuer || '') + keys[key] = { + label: key, currency: c.currency, - issuer: c.issuer, - }; - }); + issuer: c.issuer + } + }) } for (date in cache.byDate) { - list = {}; + list = {} + + source = cache.byDate[date]['Payment Volume'] + + if (!source) { + continue + } - source = cache.byDate[date]['Payment Volume']; source.components.forEach(function(c) { - var currency = c.currency + '.' + (c.issuer || ''); + key = c.currency + '.' + (c.issuer || '') - list[currency] = c.converted_amount; - }); + list[key] = c.converted_amount + }) for (key in keys) { if (!data[key]) { - data[key] = []; + data[key] = [] } data[key].push({ date: moment(date), y: list[key] || 0 - }); + }) } } return { sets: data, keys: keys - }; + } } function filterByMarket() { - var data = {}; - var keys = {}; - var source; - var date; - var key; + var data = {} + var keys = {} + var list + var source + var date + var key for (date in cache.byDate) { - source = cache.byDate[date]['Exchange Volume']; + source = cache.byDate[date]['Exchange Volume'] + + if (!source) { + continue + } source.components.forEach(function(c) { if (c.base.currency !== filters[1] && c.counter.currency !== filters[1]) { - return; + return } - var base = c.base.currency + '.' + (c.base.issuer || ''); - var counter = c.counter.currency + '.' + (c.counter.issuer || ''); - keys[base + '.' + counter] = { - label: base + '.' + counter, - base: c.base, - counter: c.counter - }; - }); + var base = c.base.currency + '.' + (c.base.issuer || '') + var counter = c.counter.currency + '.' + (c.counter.issuer || '') + keys[base + '.' + counter] = { + label: base + '.' + counter, + base: c.base, + counter: c.counter + } + }) } for (date in cache.byDate) { - list = {}; + list = {} - source = cache.byDate[date]['Exchange Volume']; + source = cache.byDate[date]['Exchange Volume'] source.components.forEach(function(c) { - var base = c.base.currency + '.' + (c.base.issuer || ''); - var counter = c.counter.currency + '.' + (c.counter.issuer || ''); + var base = c.base.currency + '.' + (c.base.issuer || '') + var counter = c.counter.currency + '.' + (c.counter.issuer || '') - list[base + '.' + counter] = c.converted_amount; - }); + list[base + '.' + counter] = c.converted_amount + }) for (key in keys) { if (!data[key]) { - data[key] = []; + data[key] = [] } data[key].push({ date: moment(date), y: list[key] || 0 - }); + }) } } return { sets: data, keys: keys - }; + } } function filterByCurrency() { - var data = {}; - var keys = {}; - var source; - var date; - var key; + var data = {} + var keys = {} + var list + var source + var date + var key + for (date in cache.byDate) { - source = cache.byDate[date][filters[0]]; + source = cache.byDate[date][filters[0]] + + + if (!source) { + continue + } source.components.forEach(function(c) { if (c.base) { keys[c.base.currency] = { label: c.base.currency - }; + } } if (c.counter) { keys[c.counter.currency] = { label: c.counter.currency - }; + } } if (c.currency) { keys[c.currency] = { label: c.currency - }; + } } - }); + }) } for (date in cache.byDate) { - list = {}; + list = {} + + source = cache.byDate[date][filters[0]] + + if (!source) { + continue + } - source = cache.byDate[date][filters[0]]; source.components.forEach(function(c) { if (c.base) { if (!list[c.base.currency]) { - list[c.base.currency] = 0; + list[c.base.currency] = 0 } if (!list[c.counter.currency]) { - list[c.counter.currency] = 0; + list[c.counter.currency] = 0 } - list[c.base.currency] += c.converted_amount; - list[c.counter.currency] += c.converted_amount; + list[c.base.currency] += c.converted_amount + list[c.counter.currency] += c.converted_amount // handle payments } else { - list[c.currency] = c.converted_amount; + list[c.currency] = c.converted_amount } - }); + }) for (key in keys) { if (!data[key]) { - data[key] = []; + data[key] = [] } data[key].push({ date: moment(date), y: list[key] || 0 - }); + }) } } return { sets: data, keys: keys - }; + } + } + + function graphData() { + var data = {} + var date + var key + + if (!filters.length) { + data = { + 'Exchange Volume': [], + 'Payment Volume': [] + } + + for (date in cache.byDate) { + data['Exchange Volume'].push({ + date: moment(date), + y: cache.byDate[date]['Exchange Volume'] ? + cache.byDate[date]['Exchange Volume'].total : 0 + }) + } + + for (date in cache.byDate) { + data['Payment Volume'].push({ + date: moment(date), + y: cache.byDate[date]['Payment Volume'] ? + cache.byDate[date]['Payment Volume'].total : 0 + }) + } + + data = { + sets: data, + keys: { + 'Exchange Volume': { + label: 'Exchange Volume' + }, + 'Payment Volume': { + label: 'Payment Volume' + } + } + } + + } else if (filters[1] && filters[0] === 'Exchange Volume') { + data = filterByMarket() + + } else if (filters[1] && filters[0] === 'Payment Volume') { + data = filterByIssuer() + } else { + data = filterByCurrency() + } + + + for (key in data.sets) { + data.sets[key].sort(function(a, b) { + return a.date.diff(b.date) + }) + } + + cache.current = data + chart.redraw(data.sets) + return data } function drawLegend(data) { - var keys = []; + var keys = [] for (var key in data.keys) { keys.push(data.keys[key]) @@ -573,16 +514,16 @@ var TotalHistory = function (options) { var labels = legend.selectAll('.label') .data(keys, function(d) { - return d.label; - }); + return d.label + }) var labelEnter = labels.enter() .append('div') .attr('class', 'label') - .classed('market', !!keys[0].base) + .classed('market', Boolean(keys[0].base)) .classed('no-click', filters.length > 1) .style('color', function(d) { - return color(d.label); + return color(d.label) }) .on('click', applyFilter) @@ -590,87 +531,199 @@ var TotalHistory = function (options) { .attr('class', 'title') .html(function(d) { if (d.currency) { - var name = gateways.getName(d.issuer) || ''; - return (name ? name + ' ' : '') + d.currency; + var name = gateways.getName(d.issuer) || '' + return (name ? name + ' ' : '') + d.currency } else if (d.base) { - var baseIssuer = gateways.getName(d.base.issuer) || ''; - var counterIssuer = gateways.getName(d.counter.issuer) || ''; + var baseIssuer = gateways.getName(d.base.issuer) || '' + var counterIssuer = gateways.getName(d.counter.issuer) || '' var base = d.base.currency + - '' + (baseIssuer || d.base.issuer || '') + ''; + '' + (baseIssuer || d.base.issuer || '') + '' var counter = d.counter.currency + - '' + (counterIssuer || d.counter.issuer || '') + ''; + '' + (counterIssuer || d.counter.issuer || '') + '' - return '' + base + '/' + counter + ''; + return '' + base + '/' + counter + '' } return d.label - }); + }) labelEnter.append('div') .attr('class', 'subtitle') .html(function(d) { - if (d.issuer) { - return d.issuer; - } - }); + return d.issuer || '' + }) - labels.exit().remove(); + labels.exit().remove() } - function applyFilter (d) { - if (filters.length > 1) { - return; - } - - filters.push(d.label); - showCrumbs(); - var data = graphData(); - drawLegend(data); - } + /** + * showCrumbs + */ function showCrumbs() { - var list = ['Total Volume']; - list.push.apply(list, filters); + var list = ['Total Volume'] + list.push.apply(list, filters) var crumb = crumbs.selectAll('li.crumb') .data(list, function(d) { - return d; - }); + return d + }) crumb.enter().append('li') .attr('class', 'crumb') .text(function(d) { - return d; + return d }).on('click', function(d, i) { - var data; + var data if (i > 1) { - return; + return } - filters = filters.splice(0, i); - console.log(filters, i); + filters = filters.splice(0, i) - showCrumbs(); - data = graphData(); - drawLegend(data); - }); + showCrumbs() + data = graphData() + drawLegend(data) + }) - crumb.exit().remove(); + crumb.exit().remove() } - function formatData(data) { - var result = []; + /** + * applyFilter + */ + + function applyFilter(d) { + if (filters.length > 1) { + return + } + + filters.push(d.label) + showCrumbs() + var data = graphData() + drawLegend(data) + } + + chart = new StackedChart({ + div: el.append('div').attr('class', 'chart'), + title: 'Total Volume', + resize: options.resize, + formatValue: formatValue, + formatLabel: formatLabel, + dateFormat: 'MMM D, YYYY [(UTC)]', + color: color + }) + + legend = el.append('div') + .attr('class', 'legend') + + currencySelect.on('change', changeCurrency) + currencySelect.property('value', currency) + + ranges.classed('clicked', function() { + return d3.select(this).text() === range + }).on('click', changeRange) + + intervals.classed('clicked', function() { + return d3.select(this).text() === interval + }).on('click', changeInterval) + + intervals.classed('no-click', function() { + return d3.select(this).text() === 'month' + }) + + $('#datepicker_from').val(moment().subtract(1, 'month') + .subtract(1, 'day').format('MM/DD/YYYY')) + $('#datepicker_to').val(moment().format('MM/DD/YYYY')) + + customTo = $('#datepicker_to').datepicker({ + maxDate: '+0d', + minDate: '', + onSelect: loadCustomRange + }) + + customFrom = $('#datepicker_from').datepicker({ + maxDate: '-2d', + onSelect: loadCustomRange + }) + + download + .style('visibility', getIEVersion() ? 'hidden' : '') + .on('click', function() { + var csv = toCSV(cache.current.sets) + var name = crumbs.select('li:last-child').text() + + if (Modernizr.prefixed('requestFileSystem', window)) { + var blob = new Blob([csv], {'type': 'application/octet-stream'}) + this.href = window.URL.createObjectURL(blob) - data.forEach(function(d) { - var date = moment(d.start_time); - result.push({ - date: date, - y: d.total ? Number(d.total.toFixed(0)) : 0 - }); - }); + } else { + this.href = 'data:text/csvcharset=utf-8,' + escape(csv) + } + + this.download = name + '_' + interval + '_historical.csv' + this.target = '_blank' + return true + }) - return result; + /** + * load + */ + + self.load = function() { + filters = [] + chart.loading = true + chart.fadeOut() + showCrumbs() + + function loadData(metric) { + return new Promise(function(resolve, reject) { + + var end = moment.utc() + var start + + if (range === 'max') { + start = moment.utc('2013-01-01') + } else if (range === 'custom') { + start = moment.utc(customFrom.val()) + end = moment.utc(customTo.val()) + } else if (range[1] === 'y') { + start = moment.utc().subtract(1, 'years') + } else { + start = moment.utc().subtract(range[0], 'months') + } + + api[metric]({ + currency: currency, + issuer: issuer, + start: start, + end: end, + interval: interval + }, function(err, data) { + + if (err) { + reject(err) + + } else { + resolve(data.rows) + } + }) + }) + } + + Promise.all([ + loadData('getExchangeVolume'), + loadData('getPaymentVolume') + ]) + .then(cacheResult) + .then(graphData) + .then(drawLegend) + .catch(function(e) { + console.log(e) + console.trace() + chart.loading = false + }) } } diff --git a/src/index.html b/src/index.html index d001c16..bd427ab 100644 --- a/src/index.html +++ b/src/index.html @@ -3,6 +3,7 @@ Ripple Charts + diff --git a/src/less/jquery.selectbox.less b/src/less/jquery.selectbox.less index f3f84cb..76d91db 100644 --- a/src/less/jquery.selectbox.less +++ b/src/less/jquery.selectbox.less @@ -1,118 +1,119 @@ .sbHolder{ - position: relative; - height:100%; - width:100%; - outline: none; - border-radius: 4px; - -moz-border-radius: 4px; - -webkit-border-radius: 4px; - border:2px solid #f0f0f0; - font-family: "OpenSans-Light", - "Open Sans Light", - "HelveticaNeue-Light", - "Helvetica Neue Light", - "Helvetica Neue", - Helvetica, - Arial, - "Lucida Grande", - sans-serif; - color:#333; + position: relative; + height:100%; + width:100%; + outline: none; + border-radius: 4px; + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + border:2px solid #f0f0f0; + font-family: "OpenSans-Light", + "Open Sans Light", + "Open Sans", + "HelveticaNeue-Light", + "Helvetica Neue Light", + "Helvetica Neue", + Helvetica, + Arial, + "Lucida Grande", + sans-serif; + color:#333; } .sbSelector{ - display: block; - position: absolute; - height: 100%; - width: 100%; - background-color:white; - overflow: hidden; - text-indent: 20px; - line-height:42px; + display: block; + position: absolute; + height: 100%; + width: 100%; + background-color:white; + overflow: hidden; + text-indent: 20px; + line-height:42px; } .sbSelector:link, .sbSelector:visited, .sbSelector:hover { - color: inherit; - text-decoration: none; + color: inherit; + text-decoration: none; } .sbToggle{ - display: block; - z-index:1; - position: absolute; - right:0; - height: 100%; - width: 44px; - background: url('icons/downward-triangle.svg'), - -webkit-gradient( - linear, - left bottom, - left top, - color-stop(0, #e9e9e9), - color-stop(1, #f8f8f8)) !important; - background: url('icons/downward-triangle.svg'), - -moz-linear-gradient( - center bottom, - #e9e9e9 0%, - #f8f8f8 100%) - repeat scroll 0% 0% padding-box transparent !important; - background-repeat:no-repeat; - border-left: 2px solid #eee; + display: block; + z-index:1; + position: absolute; + right:0; + height: 100%; + width: 44px; + background: url('icons/downward-triangle.svg'), + -webkit-gradient( + linear, + left bottom, + left top, + color-stop(0, #e9e9e9), + color-stop(1, #f8f8f8)) !important; + background: url('icons/downward-triangle.svg'), + -moz-linear-gradient( + center bottom, + #e9e9e9 0%, + #f8f8f8 100%) + repeat scroll 0% 0% padding-box transparent !important; + background-repeat:no-repeat; + border-left: 2px solid #eee; } .sbToggleOpen{ - background: url('icons/downward-triangle.svg'), - -webkit-gradient( - linear, - left bottom, - left top, - color-stop(0, #e6e6e6), - color-stop(1, #f5f5f5)); - background: url('icons/downward-triangle.svg'), - -moz-linear-gradient( - center bottom, - #e6e6e6 0%, - #f5f5f5 100%) - repeat scroll 0% 0% padding-box transparent !important; + background: url('icons/downward-triangle.svg'), + -webkit-gradient( + linear, + left bottom, + left top, + color-stop(0, #e6e6e6), + color-stop(1, #f5f5f5)); + background: url('icons/downward-triangle.svg'), + -moz-linear-gradient( + center bottom, + #e6e6e6 0%, + #f5f5f5 100%) + repeat scroll 0% 0% padding-box transparent !important; } .sbOptions{ - z-index: 1; - position: absolute; - top: 30px; - left:-2px; - width: 100%; - margin: 0; - padding: 8px 0; - background-color:white; - min-height:0px; - overflow-y: auto; - border-radius: 4px; - -moz-border-radius: 4px; - -webkit-border-radius: 4px; - border:2px solid #f0f0f0; - text-indent: 10px; + z-index: 1; + position: absolute; + top: 30px; + left:-2px; + width: 100%; + margin: 0; + padding: 8px 0; + background-color:white; + min-height:0px; + overflow-y: auto; + border-radius: 4px; + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + border:2px solid #f0f0f0; + text-indent: 10px; } .sbOptions li{ - padding: 0 7px; + padding: 0 7px; } .sbOptions a{ - display: block; - padding: 7px 0 7px 3px; + display: block; + padding: 7px 0 7px 3px; } .sbOptions a:link, .sbOptions a:visited{ - color: inherit; - text-decoration: none; - -webkit-border-radius: 5px; - -moz-border-radius: 5px; - border-radius: 5px; + color: inherit; + text-decoration: none; + -webkit-border-radius: 5px; + -moz-border-radius: 5px; + border-radius: 5px; } .sbOptions a:hover, .sbOptions a:focus, .sbOptions a.sbFocus{ - color: inherit; - background-color:#ddd; + color: inherit; + background-color:#ddd; } .sbOptions li.last a{ - border-bottom: none; + border-bottom: none; } diff --git a/src/less/main.less b/src/less/main.less index 1e0cf79..2b8b87a 100644 --- a/src/less/main.less +++ b/src/less/main.less @@ -41,7 +41,7 @@ @import 'variables.less'; body { - font-family: "Open Sans", Helvetica, Arial, sans-serif; + font-family: "Open Sans", Arial, sans-serif; padding:0 !important; line-height:inherit; width:100%; @@ -966,7 +966,7 @@ ul.dd-options li:hover { .dd-option, .dd-selected { color: #000; - font-family: "Open Sans"; + font-family: "Open Sans", Arial, Sans-serif; padding: 0 10px !important; &:hover { @@ -976,7 +976,7 @@ ul.dd-options li:hover { .dd-option-text { color: #000; - font-family: "Open Sans"; + font-family: "Open Sans", Arial, Sans-serif; &:hover { color: #000; diff --git a/src/less/orderBook.less b/src/less/orderBook.less index 88e786b..1f52a5a 100644 --- a/src/less/orderBook.less +++ b/src/less/orderBook.less @@ -109,7 +109,7 @@ width:auto; overflow:hidden; font-size:13px; - font-family:"Open Sans"; + font-family:"Open Sans", Arial; color:#223; margin:5px 60px 20px; opacity: .5; diff --git a/src/less/tradeFeed.less b/src/less/tradeFeed.less index cccf433..52d2d90 100644 --- a/src/less/tradeFeed.less +++ b/src/less/tradeFeed.less @@ -14,7 +14,7 @@ .price .amount { font-size:40px; - font-family: "Open Sans Light"; + font-family: "Open Sans Light", "Open Sans", Arial; padding:0 5px; color:#444; } @@ -83,7 +83,7 @@ border-left:none; border-spacing:1px; border-collapse:collapse; - font-family:"Open Sans"; + font-family: "Open Sans", Arial; font-size:13px; td {