From f903235c246ef503df9274b52fdd59f98ea60879 Mon Sep 17 00:00:00 2001 From: marcdeb1 <29503179+marcdeb1@users.noreply.github.com> Date: Fri, 19 Oct 2018 20:22:50 +0800 Subject: [PATCH] Added mining inflation chart to stats page (#34) --- src/Template/Main/blocks.ctp | 231 +-------------------- src/Template/Main/stats.ctp | 29 ++- webroot/css/mining-inflation-chart.css | 8 + webroot/js/block-size-chart.js | 228 +++++++++++++++++++++ webroot/js/mining-inflation-chart.js | 265 +++++++++++++++++++++++++ 5 files changed, 524 insertions(+), 237 deletions(-) create mode 100644 webroot/css/mining-inflation-chart.css create mode 100644 webroot/js/block-size-chart.js create mode 100644 webroot/js/mining-inflation-chart.js diff --git a/src/Template/Main/blocks.ctp b/src/Template/Main/blocks.ctp index b662378..38b8543 100644 --- a/src/Template/Main/blocks.ctp +++ b/src/Template/Main/blocks.ctp @@ -134,236 +134,7 @@ - + end(); ?>
diff --git a/src/Template/Main/stats.ctp b/src/Template/Main/stats.ctp index 36e056b..e10122d 100644 --- a/src/Template/Main/stats.ctp +++ b/src/Template/Main/stats.ctp @@ -1,19 +1,34 @@ assign('title', 'Stats & Rich List') ?> -start('script'); ?> - -end(); ?> - element('header') ?> +start('script'); ?> + + + + + +end(); ?> + +start('css'); + echo $this->Html->css('/css/mining-inflation-chart.css'); + echo $this->Html->css('https://www.amcharts.com/lib/3/plugins/export/export.css'); + $this->end(); + ?>

LBRY Stats

-
+
+
+
+

Mining Inflation Chart

+
+
+
+

LBRY Rich List (Top 500)

diff --git a/webroot/css/mining-inflation-chart.css b/webroot/css/mining-inflation-chart.css new file mode 100644 index 0000000..92f1b34 --- /dev/null +++ b/webroot/css/mining-inflation-chart.css @@ -0,0 +1,8 @@ +.mining-inflation-chart-container { width: 1200px; margin: 0 auto 48px auto; box-shadow: 0 2px 6px rgba(0,0,0,.175); border: 1px solid rgba(0,0,0,.15); padding: 24px 36px; position: relative; overflow: hidden } +.mining-inflation-chart-container .load-progress { position: absolute; top: 0; left: 0; width: 100%; height: 3px; background: #1e88e5; animation: indeterminate 4s linear infinite; } +.mining-inflation-chart-container .chart { height: 414px } +.mining-inflation-chart-container .btn-chart-export { position: absolute; right: 40px; bottom: 36px } +@keyframes indeterminate { + from { left: -70%; } + to { left: 100% } +} \ No newline at end of file diff --git a/webroot/js/block-size-chart.js b/webroot/js/block-size-chart.js new file mode 100644 index 0000000..24aba9a --- /dev/null +++ b/webroot/js/block-size-chart.js @@ -0,0 +1,228 @@ +var chart; +var chartData = []; +var chartLoadInProgress = false; +var minPeriod = 'hh'; +var validPeriods = ['24h', '72h', '168h', '30d', '90d', '1y']; +var defaultPeriod = (validPeriods.indexOf(localStorage.getItem('chartPeriod')) > -1) ? localStorage.getItem('chartPeriod') : '24h'; +var periodGridCounts = {'24h': 24, '72h': 24, '168h': 14, '30d': 30, '90d': 45, '1y': 12 }; +AmCharts.ready(function() { + chart = AmCharts.makeChart('block-size-chart', { + type: 'serial', + theme: 'light', + mouseWheelZoomEnabled: true, + categoryField: 'date', + synchronizeGrid: true, + dataProvider: chartData, + valueAxes: [ + { + id: 'v-block-size', + axisColor: '#1e88e5', + axisThickness: 2, + labelFunction: function(value) { + return (Math.round((value / 1000) * 100)/100).toFixed(2) + ' KB'; + } + }, + { + id: 'v-price', + axisColor: '#00e676', + offset: 75, + gridAlpha: 0, + axisThickness: 2, + labelFunction: function(value) { + return '$' + value.toFixed(2); + } + } + ], + categoryAxis: { + parseDates: true, + minPeriod: minPeriod, // DD for daily + autoGridCount: false, + minorGridEnabled: true, + minorGridAlpha: 0.04, + axisColor: '#dadada', + twoLineMode: true, + dateFormats: [{ + period: 'fff', + format: 'JJ:NN:SS' + }, { + period: 'ss', + format: 'JJ:NN:SS' + }, { + period: 'mm', + format: 'JJ:NN' + }, { + period: 'hh', + format: 'JJ:NN' + }, { + period: 'DD', + format: 'DD' + }, { + period: 'WW', + format: 'DD MMM' + }, { + period: 'MM', + format: 'MMM' + }, { + period: 'YYYY', + format: 'YYYY' + }] + }, + graphs: [ + { + id: 'g-block-size', + valueAxis: 'v-block-size', // we have to indicate which value axis should be used + title: 'Avg Block Size', + valueField: 'AvgBlockSize', + bullet: 'round', + bulletBorderThickness: 1, + bulletBorderAlpha: 1, + bulletColor: '#ffffff', + bulletSize: 5, + useLineColorForBulletBorder: true, + lineColor: '#1e88e5', + hideBulletsCount: 101, + balloonText: '[[AvgBlockSize]] KB', + switchable: false, + balloonFunction: function(item, graph) { + var result = graph.balloonText; + return result.replace('[[AvgBlockSize]]', (Math.round((item.dataContext.AvgBlockSize / 1000) * 100)/100).toFixed(2)); + } + }, + { + id: 'g-price', + valueAxis: 'v-price', + title: 'Average Price', + valueField: 'AvgUSD', + bullet: 'round', + bulletBorderThickness: 1, + bulletBorderAlpha: 1, + bulletColor: '#ffffff', + bulletSize: 5, + useLineColorForBulletBorder: true, + lineColor: '#00e676', + balloonText: '$[[AvgUSD]]', + balloonFunction: function(item, graph) { + var result = graph.balloonText; + if (!item.dataContext.AvgUSD) { + return ''; + } + return result.replace('[[AvgUSD]]', item.dataContext.AvgUSD.toFixed(2)); + }, + hideBulletsCount: 101, + labelFunction: function(value) { + return '$' + value; + }, + } + ], + chartCursor: { + cursorAlpha: 0.1, + fullWidth: true, + valueLineBalloonEnabled: true, + categoryBalloonColor: '#333333', + cursorColor: '#1e88e5', + categoryBalloonDateFormat: minPeriod === 'hh' ? 'D MMM HH:NN ' : 'D MMM' + }, + chartScrollbar: { + scrollbarHeight: 36, + color: '#888888', + gridColor: '#bbbbbb' + }, + legend: { + marginLeft: 110, + useGraphSettings: true, + valueAlign: 'right', + valueWidth: 60, + spacing: 64, + valueFunction: function(item, formatted) { + if (item.dataContext) { + var g = item.graph; + if (g.id === 'g-block-size' && item.dataContext.AvgBlockSize > 0) { + return g.balloonText.replace('[[AvgBlockSize]]', (Math.round((item.dataContext.AvgBlockSize / 1000) * 100)/100).toFixed(2) ); + } + if (g.id === 'g-price' && item.dataContext.AvgUSD) { + return g.balloonText.replace('[[AvgUSD]]', item.dataContext.AvgUSD.toFixed(2)); + } + } + + return formatted; + } + }, + export: { + enabled: true, + fileName: 'lbry-block-size-chart', + position: 'bottom-right', + divId: 'chart-export' + } + }); + + loadChartData(defaultPeriod); +}); + +var loadChartData = function(dataPeriod) { + var loadProgress = $('.block-size-chart-container .load-progress'); + // clear previous chart data + $.ajax({ + url: '/api/v1/charts/blocksize/' + dataPeriod, + type: 'get', + dataType: 'json', + beforeSend: function() { + chartLoadInProgress = true; + loadProgress.css({ display: 'block' }); + }, + success: function(response) { + if (response.success) { + chartData = []; + var data = response.data; + for (var period in data) { + if (data.hasOwnProperty(period)) { + chartData.push({ + date: Date.parse(period), + AvgBlockSize: data[period].AvgBlockSize, + AvgUSD: data[period].AvgUSD + }); + } + } + + // save selcted period to localStorage + localStorage.setItem('chartPeriod', dataPeriod); + + if (chart) { + var isHourly = (dataPeriod.indexOf('h') > -1); + var gridCount = periodGridCounts[dataPeriod]; + chart.categoryAxis.minPeriod = isHourly ? 'hh' : 'DD'; + chart.categoryAxis.dateFormats[4].format = isHourly ? 'DD MMM' : 'DD'; + chart.chartCursor.categoryBalloonDateFormat = isHourly ? 'D MMM HH:NN ' : 'D MMM YYYY'; + chart.categoryAxis.gridCount = gridCount; + chart.chartScrollbar.gridCount = gridCount; + chart.dataProvider = chartData; + chart.validateNow(); + chart.validateData(); + } + } + }, + complete: function() { + chartLoadInProgress = false; + loadProgress.css({ display: 'none' }); + } + }); +}; + +$(document).ready(function() { + $('.block-size-data-links a').on('click', function(evt) { + evt.preventDefault(); + if (chartLoadInProgress) { + return; + } + + var link = $(this); + if (link.hasClass('active')) { + return; + } + + link.addClass('active').siblings().removeClass('active'); + var period = link.attr('data-period'); + loadChartData(period); + }); + + $('a[data-period="' + defaultPeriod + '"]').addClass('active').siblings().removeClass('active'); +}); \ No newline at end of file diff --git a/webroot/js/mining-inflation-chart.js b/webroot/js/mining-inflation-chart.js new file mode 100644 index 0000000..e43411c --- /dev/null +++ b/webroot/js/mining-inflation-chart.js @@ -0,0 +1,265 @@ +function getReward(blockHeight) { + if (blockHeight == 0) { + return 400000000; + } + else if (blockHeight <= 5100) { + return 1; + } + else if (blockHeight <= 55000) { + return 1 + Math.floor((blockHeight - 5001) / 100); + } + else { + var level = Math.floor((blockHeight - 55001) / 32); + var reduction = Math.floor((Math.floor(Math.sqrt((8 * level) + 1)) - 1) / 2); + while(!(withinLevelBounds(reduction, level))) { + if(Math.floor((reduction * reduction + reduction) / 2) > level) { + reduction--; + } + else { + reduction++; + } + } + return Math.max(0, 500 - reduction); + } +} + +function withinLevelBounds(reduction, level) { + if(Math.floor((reduction * reduction + reduction) / 2) > level) { + return false; + } + reduction += 1; + if(Math.floor((reduction * reduction + reduction) / 2) <= level) { + return false; + } + return true; +} + +function getAverageBlockTime(blocks) { + var numBlocks = blocks.length; + var windowSize = 100; + var sum = 0; + for(i = numBlocks - windowSize; i < numBlocks; i++) { + sum += blocks[i].block_time - blocks[i-1].block_time; + } + return sum / windowSize; +} + +function buildChartData(blockData) { + var chartData = []; + var supply = 0; + var reward = 0; + var averageBlockTime = getAverageBlockTime(blockData); + var blockTime = 0; + var lastBlock = 4071017; // Last block with reward + var skip = 100; + var blocksPerYear = Math.floor((3600*24*365) / averageBlockTime); + var historicalSupply = {}; + var lastYearSupply = 0; + var lastYearBlock = 0; + var inflationRate = 0; + for(var i = 0; i < lastBlock; i++) { + reward = getReward(i); + supply += reward; + historicalSupply[i + 1] = supply; + if(i == 0) { // Reward for 1st block set to 0 for scale + reward = 0; + } + if(i < blockData.length) { + // Historical Data + var b = blockData[i]; + blockTime = b.block_time; + } + else { + // Future blocks + skip = 1000; + blockTime += averageBlockTime; + } + // Inflation Rate + if(i + 1 - blocksPerYear <= 0) { + lastYearBlock = 1; + } + else { + lastYearBlock = i + 1 - blocksPerYear; + } + lastYearSupply = historicalSupply[lastYearBlock]; + inflationRate = ((supply - lastYearSupply) / lastYearSupply) * 100; + if(i % skip == 0) { // Only push 1/ of all blocks to optimize data loading + chartData.push({ + date: new Date(blockTime * 1000), + date: new Date(blockTime * 1000), + AvailableSupply: supply, + RewardLBC: reward, + InflationRate: inflationRate, + BlockId: i + 1 + }); + } + } + return chartData; +} + +function loadChartData() { + var api_url = "https://chainquery.lbry.io/api/sql?query="; + var query = "SELECT id, block_time FROM block"; + var url = api_url + query; + var loadProgress = $('.mining-inflation-chart-container .load-progress'); + $.ajax({ + url: url, + type: 'get', + dataType: 'json', + beforeSend: function() { + chartLoadInProgress = true; + loadProgress.css({ display: 'block' }); + }, + success: function(response) { + if(response.success) { + chartData = buildChartData(response.data); + if(chart) { + chart.dataProvider = chartData; + chart.validateNow(); + chart.validateData(); + } + } + else { + console.log("Could not fetch block data."); + } + }, + complete: function() { + chartLoadInProgress = false; + loadProgress.css({ display: 'none' }); + } +}); +} + +var chart; +var chartData = []; +var chartLoadInProgress = false; +AmCharts.ready(function() { +chart = AmCharts.makeChart('mining-inflation-chart', { +type: 'serial', +theme: 'light', +mouseWheelZoomEnabled: true, +height: '100%', +categoryField: 'date', +synchronizeGrid: true, +dataProvider: chartData, +responsive: { + enabled: true, +}, +valueAxes: [ +{ + id: 'v-supply', + axisColor: '#1e88e5', + axisThickness: 2, + position: 'left', + labelFunction: function(value) { + return (Math.round((value / 1000000) * 1000000)/1000000).toFixed(2); + } +}, +{ + id: 'v-reward', + axisColor: '#0b7a06', + axisThickness: 2, + position: 'left', + offset: 75, +}, +{ + id: 'v-inflation-rate', + axisColor: '#ff9900', + axisThickness: 2, + position: 'right', + labelFunction: function(value) { + return value.toFixed(2); + } +}, +], +categoryAxis: { +parseDates: true, +autoGridCount: false, +minorGridEnabled: true, +minorGridAlpha: 0.04, +axisColor: '#dadada', +twoLineMode: true +}, +graphs: [ +{ + id: 'g-supply', + valueAxis: 'v-supply', // we have to indicate which value axis should be used + title: 'Available supply (millions LBC)', + valueField: 'AvailableSupply', + bullet: 'round', + bulletBorderThickness: 1, + bulletBorderAlpha: 1, + bulletColor: '#ffffff', + bulletSize: 5, + useLineColorForBulletBorder: true, + lineColor: '#1e88e5', + hideBulletsCount: 101, + balloonText: '[[AvailableSupply]]', + balloonFunction: function(item, graph) { + var result = graph.balloonText; + return result.replace('[[AvailableSupply]]', (Math.round((item.dataContext.AvailableSupply / 1000000) * 1000000)/1000000).toFixed(2)); + } +}, +{ + id: 'g-reward', + valueAxis: 'v-reward', + title: 'Block Reward (LBC)', + valueField: 'RewardLBC', + bullet: 'round', + bulletBorderThickness: 1, + bulletBorderAlpha: 1, + bulletColor: '#ffffff', + bulletSize: 5, + useLineColorForBulletBorder: true, + lineColor: '#0b7a06', + balloonText: '[[RewardLBC]] LBC
Block [[BlockId]]', + hideBulletsCount: 101 +}, +{ + id: 'g-inflation-rate', + valueAxis: 'v-inflation-rate', + title: 'Annualized Inflation Rate', + valueField: 'InflationRate', + bullet: 'round', + bulletBorderThickness: 1, + bulletBorderAlpha: 1, + bulletColor: '#ffffff', + bulletSize: 5, + useLineColorForBulletBorder: true, + lineColor: '#ff9900', + balloonText: '[[InflationRate]]%', + hideBulletsCount: 101, + balloonFunction: function(item, graph) { + var result = graph.balloonText; + return result.replace('[[InflationRate]]', item.dataContext.InflationRate.toFixed(2)); + } +} +], +chartCursor: { +cursorAlpha: 0.1, +fullWidth: true, +valueLineBalloonEnabled: true, +categoryBalloonColor: '#333333', +cursorColor: '#1e88e5' +}, +chartScrollbar: { +scrollbarHeight: 36, +color: '#888888', +gridColor: '#bbbbbb' +}, +legend: { +marginLeft: 110, +useGraphSettings: true, +valueText: "", +spacing: 64, + +}, +export: { +enabled: true, +fileName: 'lbry-supply-chart', +position: 'bottom-right', +divId: 'chart-export' +} +}); +loadChartData(); +}); \ No newline at end of file