diff --git a/public/include/classes/statistics.class.php b/public/include/classes/statistics.class.php index 0161c139..5eae4cff 100644 --- a/public/include/classes/statistics.class.php +++ b/public/include/classes/statistics.class.php @@ -139,6 +139,31 @@ class Statistics { $this->debug->append("Failed to fetch hashrate: " . $this->mysqli->error); return false; } -} + public function getHourlyHashrateByAccount($account_id) { + $stmt = $this->mysqli->prepare(" + SELECT + ROUND(COUNT(s.id) * POW(2, 12)/600/1000) AS hashrate, + HOUR(s.time) AS hour + FROM " . $this->share->getTableName() . " AS s, accounts AS a + WHERE time < NOW() - INTERVAL 1 HOUR AND time > NOW() - INTERVAL 25 HOUR + AND a.username = SUBSTRING_INDEX( s.username, '.', 1 ) + AND a.id = ? + GROUP BY HOUR(time) + UNION ALL + SELECT + ROUND(COUNT(s.id) * POW(2, 12)/600/1000) AS hashrate, + HOUR(s.time) AS hour + FROM " . $this->share->getArchiveTableName() . " AS s, accounts AS a + WHERE time < NOW() - INTERVAL 1 HOUR AND time > NOW() - INTERVAL 25 HOUR + AND a.username = SUBSTRING_INDEX( s.username, '.', 1 ) + AND a.id = ? + GROUP BY HOUR(time)"); + if ($this->checkStmt($stmt) && $stmt->bind_param("ii", $account_id, $account_id) && $stmt->execute() && $hourlyhashrates = $stmt->get_result()) + return $hourlyhashrates->fetch_all(MYSQLI_ASSOC); + // Catchall + $this->debug->append("Failed to fetch hourly hashrate: " . $this->mysqli->error); + return false; + } +} $statistics = new Statistics($debug, $mysqli, $config, $share, $user); diff --git a/public/include/pages/statistics/pool.inc.php b/public/include/pages/statistics/pool.inc.php index bc12c3af..822eb3c0 100644 --- a/public/include/pages/statistics/pool.inc.php +++ b/public/include/pages/statistics/pool.inc.php @@ -21,30 +21,23 @@ if ($bitcoin->can_connect() === true){ } if (!$aHashData = $memcache->get('aHashData')) { - $debug->append('Hashrates expired in memcache'); + $debug->append('STA Fetching Hashrates from database'); // Top 15 hashrate list - $stmt = $mysqli->prepare("SELECT + $stmt = $mysqli->prepare(" + SELECT + COUNT(id) AS shares, ROUND(COUNT(id) * POW(2," . $config['difficulty'] . ")/600/1000,2) AS hashrate, SUBSTRING_INDEX( `username` , '.', 1 ) AS account - FROM shares - WHERE time > DATE_SUB(now(), INTERVAL 10 MINUTE) - GROUP BY account - ORDER BY hashrate DESC LIMIT 15"); + FROM shares + WHERE time > DATE_SUB(now(), INTERVAL 10 MINUTE) + GROUP BY account + ORDER BY hashrate DESC LIMIT 15"); $stmt->execute(); $hashrates= $stmt->get_result(); $aHashData = $hashrates->fetch_all(MYSQLI_ASSOC); $stmt->close(); $memcache->set('aHashData', $aHashData, 60); -} - -if (! $aContributerData = $memcache->get('aContributerData') ) { - // Top 15 Contributers - $stmt = $mysqli->prepare("SELECT count(id) AS shares, SUBSTRING_INDEX( `username` , '.', 1 ) AS account FROM shares GROUP BY account ORDER BY shares DESC LIMIT 15"); - $stmt->execute(); - $contributers = $stmt->get_result(); - $aContributerData = $contributers->fetch_all(MYSQLI_ASSOC); - $stmt->close(); - $memcache->set('aContributerData', $aContributerData, 60); + $debug->append('END Fetching Hashrates from database'); } // Grab the last block found diff --git a/public/include/pages/statistics/user.inc.php b/public/include/pages/statistics/user.inc.php new file mode 100644 index 00000000..e690fc80 --- /dev/null +++ b/public/include/pages/statistics/user.inc.php @@ -0,0 +1,37 @@ +can_connect() === true){ + if (!$dDifficulty = $memcache->get('dDifficulty')) { + $dDifficulty = $bitcoin->query('getdifficulty'); + $memcache->set('dDifficulty', $dDifficulty, 60); + } + if (!$iBlock = $memcache->get('iBlock')) { + $iBlock = $bitcoin->query('getblockcount'); + $memcache->set('iBlock', $iBlock, 60); + } +} else { + $iDifficulty = 1; + $iBlock = 0; + $_SESSION['POPUP'][] = array('CONTENT' => 'Unable to connect to pushpool service: ' . $bitcoin->can_connect(), 'TYPE' => 'errormsg'); +} + +if (!$aHourlyHashRates = $memcache->get('mmcfe_' . $_SESSION['USERDATA']['id'] . '_hourlyhashrate')) { + $debug->append('STA Fetching hourly hashrates from database'); + $aHourlyHashRates = $statistics->getHourlyHashrateByAccount($_SESSION['USERDATA']['id']); + $memcache->set('mmcfe_' . $_SESSION['USERDATA']['id'] . '_hourlyhashrate', $aHourlyHashRates, 600); + $debug->append('END Fetching hourly hashrates from database'); +} + +// Propagate content our template +$smarty->assign("YOURHASHRATES", $aHourlyHashRates); +$smarty->assign("DIFFICULTY", $dDifficulty); + +if ($_SESSION['AUTHENTICATED']) { + $smarty->assign("CONTENT", "default.tpl"); +} +?> diff --git a/public/site_assets/mmcFE/js/jquery.tablesorter.pager.js b/public/site_assets/mmcFE/js/jquery.tablesorter.pager.js new file mode 100644 index 00000000..cce6ea26 --- /dev/null +++ b/public/site_assets/mmcFE/js/jquery.tablesorter.pager.js @@ -0,0 +1,184 @@ +(function($) { + $.extend({ + tablesorterPager: new function() { + + function updatePageDisplay(c) { + var s = $(c.cssPageDisplay,c.container).val((c.page+1) + c.seperator + c.totalPages); + } + + function setPageSize(table,size) { + var c = table.config; + c.size = size; + c.totalPages = Math.ceil(c.totalRows / c.size); + c.pagerPositionSet = false; + moveToPage(table); + fixPosition(table); + } + + function fixPosition(table) { + var c = table.config; + if(!c.pagerPositionSet && c.positionFixed) { + var c = table.config, o = $(table); + if(o.offset) { + c.container.css({ + top: o.offset().top + o.height() + 'px', + position: 'absolute' + }); + } + c.pagerPositionSet = true; + } + } + + function moveToFirstPage(table) { + var c = table.config; + c.page = 0; + moveToPage(table); + } + + function moveToLastPage(table) { + var c = table.config; + c.page = (c.totalPages-1); + moveToPage(table); + } + + function moveToNextPage(table) { + var c = table.config; + c.page++; + if(c.page >= (c.totalPages-1)) { + c.page = (c.totalPages-1); + } + moveToPage(table); + } + + function moveToPrevPage(table) { + var c = table.config; + c.page--; + if(c.page <= 0) { + c.page = 0; + } + moveToPage(table); + } + + + function moveToPage(table) { + var c = table.config; + if(c.page < 0 || c.page > (c.totalPages-1)) { + c.page = 0; + } + + renderTable(table,c.rowsCopy); + } + + function renderTable(table,rows) { + + var c = table.config; + var l = rows.length; + var s = (c.page * c.size); + var e = (s + c.size); + if(e > rows.length ) { + e = rows.length; + } + + + var tableBody = $(table.tBodies[0]); + + // clear the table body + + $.tablesorter.clearTableBody(table); + + for(var i = s; i < e; i++) { + + //tableBody.append(rows[i]); + + var o = rows[i]; + var l = o.length; + for(var j=0; j < l; j++) { + + tableBody[0].appendChild(o[j]); + + } + } + + fixPosition(table,tableBody); + + $(table).trigger("applyWidgets"); + + if( c.page >= c.totalPages ) { + moveToLastPage(table); + } + + updatePageDisplay(c); + } + + this.appender = function(table,rows) { + + var c = table.config; + + c.rowsCopy = rows; + c.totalRows = rows.length; + c.totalPages = Math.ceil(c.totalRows / c.size); + + renderTable(table,rows); + }; + + this.defaults = { + size: 10, + offset: 0, + page: 0, + totalRows: 0, + totalPages: 0, + container: null, + cssNext: '.next', + cssPrev: '.prev', + cssFirst: '.first', + cssLast: '.last', + cssPageDisplay: '.pagedisplay', + cssPageSize: '.pagesize', + seperator: "/", + positionFixed: true, + appender: this.appender + }; + + this.construct = function(settings) { + + return this.each(function() { + + config = $.extend(this.config, $.tablesorterPager.defaults, settings); + + var table = this, pager = config.container; + + $(this).trigger("appendCache"); + + config.size = parseInt($(".pagesize",pager).val()); + + $(config.cssFirst,pager).click(function() { + moveToFirstPage(table); + return false; + }); + $(config.cssNext,pager).click(function() { + moveToNextPage(table); + return false; + }); + $(config.cssPrev,pager).click(function() { + moveToPrevPage(table); + return false; + }); + $(config.cssLast,pager).click(function() { + moveToLastPage(table); + return false; + }); + $(config.cssPageSize,pager).change(function() { + setPageSize(table,parseInt($(this).val())); + return false; + }); + }); + }; + + } + }); + // extend plugin scope + $.fn.extend({ + tablesorterPager: $.tablesorterPager.construct + }); + +})(jQuery); \ No newline at end of file diff --git a/public/site_assets/mmcFE/js/jquery.visualize.js b/public/site_assets/mmcFE/js/jquery.visualize.js new file mode 100644 index 00000000..3daf800a --- /dev/null +++ b/public/site_assets/mmcFE/js/jquery.visualize.js @@ -0,0 +1,806 @@ +/** + * -------------------------------------------------------------------- + * jQuery-Plugin "visualize" + * by Scott Jehl, scott@filamentgroup.com + * http://www.filamentgroup.com + * Copyright (c) 2009 Filament Group + * Dual licensed under the MIT (filamentgroup.com/examples/mit-license.txt) and GPL (filamentgroup.com/examples/gpl-license.txt) licenses. + * + * -------------------------------------------------------------------- + */ +(function ($) { + $.fn.visualize = function (options, container) { + return $(this).each(function () { + //configuration + var o = $.extend({ + type: 'bar', + //also available: area, pie, line + width: $(this).width(), + //height of canvas - defaults to table height + height: $(this).height(), + //height of canvas - defaults to table height + appendTitle: true, + //table caption text is added to chart + title: null, + //grabs from table caption if null + appendKey: true, + //color key is added to chart + colors: ['#be1e2d', '#666699', '#92d5ea', '#ee8310', '#8d10ee', '#5a3b16', '#26a4ed', '#f45a90', '#e9e744'], + textColors: [], + //corresponds with colors array. null/undefined items will fall back to CSS + parseDirection: 'x', + //which direction to parse the table data + pieMargin: 10, + //pie charts only - spacing around pie + pieLabelsAsPercent: true, + pieLabelPos: 'inside', + lineWeight: 4, + //for line and area - stroke weight + lineDots: false, + //also available: 'single', 'double' + dotInnerColor: "#ffffff", + // only used for lineDots:'double' + lineMargin: (options.lineDots ? 15 : 0), + //for line and area - spacing around lines + barGroupMargin: 10, + chartId: '', + xLabelParser: null, + // function to parse labels as values + valueParser: null, + // function to parse values. must return a Number + chartId: '', + chartClass: '', + barMargin: 1, + //space around bars in bar chart (added to both sides of bar) + yLabelInterval: 30, + //distance between y labels + interaction: false // only used for lineDots != false -- triggers mouseover and mouseout on original table + }, options); + + //reset width, height to numbers + o.width = parseFloat(o.width); + o.height = parseFloat(o.height); + + // reset padding if graph is not lines + if (o.type != 'line' && o.type != 'area') { + o.lineMargin = 0; + } + + var self = $(this); + + // scrape data from html table + var tableData = {}; + var colors = o.colors; + var textColors = o.textColors; + + + var parseLabels = function (direction) { + var labels = []; + if (direction == 'x') { + self.find('thead tr').each(function (i) { + $(this).find('th').each(function (j) { + if (!labels[j]) { + labels[j] = []; + } + labels[j][i] = $(this).text() + }) + }); + } else { + self.find('tbody tr').each(function (i) { + $(this).find('th').each(function (j) { + if (!labels[i]) { + labels[i] = []; + } + labels[i][j] = $(this).text() + }); + }); + } + return labels; + }; + + var fnParse = o.valueParser || parseFloat; + var dataGroups = tableData.dataGroups = []; + if (o.parseDirection == 'x') { + self.find('tbody tr').each(function (i, tr) { + dataGroups[i] = {}; + dataGroups[i].points = []; + dataGroups[i].color = colors[i]; + if (textColors[i]) { + dataGroups[i].textColor = textColors[i]; + } + $(tr).find('td').each(function (j, td) { + dataGroups[i].points.push({ + value: fnParse($(td).text()), + elem: td, + tableCords: [i, j] + }); + }); + }); + } else { + var cols = self.find('tbody tr:eq(0) td').size(); + for (var i = 0; i < cols; i++) { + dataGroups[i] = {}; + dataGroups[i].points = []; + dataGroups[i].color = colors[i]; + if (textColors[i]) { + dataGroups[i].textColor = textColors[i]; + } + self.find('tbody tr').each(function (j) { + dataGroups[i].points.push({ + value: $(this).find('td').eq(i).text() * 1, + elem: this, + tableCords: [i, j] + }); + }); + }; + } + + + var allItems = tableData.allItems = []; + $(dataGroups).each(function (i, row) { + var count = 0; + $.each(row.points, function (j, point) { + allItems.push(point); + count += point.value; + }); + row.groupTotal = count; + }); + + tableData.dataSum = 0; + tableData.topValue = 0; + tableData.bottomValue = Infinity; + $.each(allItems, function (i, item) { + tableData.dataSum += fnParse(item.value); + if (fnParse(item.value, 10) > tableData.topValue) { + tableData.topValue = fnParse(item.value, 10); + } + if (item.value < tableData.bottomValue) { + tableData.bottomValue = fnParse(item.value); + } + }); + var dataSum = tableData.dataSum; + var topValue = tableData.topValue; + var bottomValue = tableData.bottomValue; + + var xAllLabels = tableData.xAllLabels = parseLabels(o.parseDirection); + var yAllLabels = tableData.yAllLabels = parseLabels(o.parseDirection === 'x' ? 'y' : 'x'); + + var xLabels = tableData.xLabels = []; + $.each(tableData.xAllLabels, function (i, labels) { + tableData.xLabels.push(labels[0]); + }); + + var totalYRange = tableData.totalYRange = tableData.topValue - tableData.bottomValue; + + var zeroLocX = tableData.zeroLocX = 0; + + if ($.isFunction(o.xLabelParser)) { + + var xTopValue = null; + var xBottomValue = null; + + $.each(xLabels, function (i, label) { + label = xLabels[i] = o.xLabelParser(label); + if (i === 0) { + xTopValue = label; + xBottomValue = label; + } + if (label > xTopValue) { + xTopValue = label; + } + if (label < xBottomValue) { + xBottomValue = label; + } + }); + + var totalXRange = tableData.totalXRange = xTopValue - xBottomValue; + + + var xScale = tableData.xScale = (o.width - 2 * o.lineMargin) / totalXRange; + var marginDiffX = 0; + if (o.lineMargin) { + var marginDiffX = -2 * xScale - o.lineMargin; + } + zeroLocX = tableData.zeroLocX = xBottomValue + o.lineMargin; + + tableData.xBottomValue = xBottomValue; + tableData.xTopValue = xTopValue; + tableData.totalXRange = totalXRange; + } + + var yScale = tableData.yScale = (o.height - 2 * o.lineMargin) / totalYRange; + var zeroLocY = tableData.zeroLocY = (o.height - 2 * o.lineMargin) * (tableData.topValue / tableData.totalYRange) + o.lineMargin; + + var yLabels = tableData.yLabels = []; + + var numLabels = Math.floor((o.height - 2 * o.lineMargin) / 30); + + var loopInterval = tableData.totalYRange / numLabels; //fix provided from lab + loopInterval = Math.round(parseFloat(loopInterval) / 5) * 5; + loopInterval = Math.max(loopInterval, 1); + + // var start = + for (var j = Math.round(parseInt(tableData.bottomValue) / 5) * 5; j <= tableData.topValue - loopInterval; j += loopInterval) { + yLabels.push(j); + } + if (yLabels[yLabels.length - 1] > tableData.topValue + loopInterval) { + yLabels.pop(); + } else if (yLabels[yLabels.length - 1] <= tableData.topValue - 10) { + yLabels.push(tableData.topValue); + } + + // populate some data + $.each(dataGroups, function (i, row) { + row.yLabels = tableData.yAllLabels[i]; + $.each(row.points, function (j, point) { + point.zeroLocY = tableData.zeroLocY; + point.zeroLocX = tableData.zeroLocX; + point.xLabels = tableData.xAllLabels[j]; + point.yLabels = tableData.yAllLabels[i]; + point.color = row.color; + }); + }); + + try { + console.log(tableData); + } catch (e) {} + + var charts = {}; + + charts.pie = { + interactionPoints: dataGroups, + + setup: function () { + charts.pie.draw(true); + }, + draw: function (drawHtml) { + + var centerx = Math.round(canvas.width() / 2); + var centery = Math.round(canvas.height() / 2); + var radius = centery - o.pieMargin; + var counter = 0.0; + + if (drawHtml) { + canvasContain.addClass('visualize-pie'); + + if (o.pieLabelPos == 'outside') { + canvasContain.addClass('visualize-pie-outside'); + } + + var toRad = function (integer) { + return (Math.PI / 180) * integer; + }; + var labels = $('').insertAfter(canvas); + } + + + //draw the pie pieces + $.each(dataGroups, function (i, row) { + var fraction = row.groupTotal / dataSum; + if (fraction <= 0 || isNaN(fraction)) return; + ctx.beginPath(); + ctx.moveTo(centerx, centery); + ctx.arc(centerx, centery, radius, counter * Math.PI * 2 - Math.PI * 0.5, (counter + fraction) * Math.PI * 2 - Math.PI * 0.5, false); + ctx.lineTo(centerx, centery); + ctx.closePath(); + ctx.fillStyle = dataGroups[i].color; + ctx.fill(); + // draw labels + if (drawHtml) { + var sliceMiddle = (counter + fraction / 2); + var distance = o.pieLabelPos == 'inside' ? radius / 1.5 : radius + radius / 5; + var labelx = Math.round(centerx + Math.sin(sliceMiddle * Math.PI * 2) * (distance)); + var labely = Math.round(centery - Math.cos(sliceMiddle * Math.PI * 2) * (distance)); + var leftRight = (labelx > centerx) ? 'right' : 'left'; + var topBottom = (labely > centery) ? 'bottom' : 'top'; + var percentage = parseFloat((fraction * 100).toFixed(2)); + + // interaction variables + row.canvasCords = [labelx, labely]; + row.zeroLocY = tableData.zeroLocY = 0; // related to zeroLocY and plugin API + row.zeroLocX = tableData.zeroLocX = 0; // related to zeroLocX and plugin API + row.value = row.groupTotal; + + + if (percentage) { + var labelval = (o.pieLabelsAsPercent) ? percentage + '%' : row.groupTotal; + var labeltext = $('' + labelval + '').css(leftRight, 0).css(topBottom, 0); + if (labeltext) var label = $('
  • ').appendTo(labels).css({ + left: labelx, + top: labely + }).append(labeltext); + labeltext.css('font-size', radius / 8).css('margin-' + leftRight, -labeltext.width() / 2).css('margin-' + topBottom, -labeltext.outerHeight() / 2); + + if (dataGroups[i].textColor) { + labeltext.css('color', dataGroups[i].textColor); + } + + } + } + counter += fraction; + }); + } + }; + + (function () { + + var xInterval; + + var drawPoint = function (ctx, x, y, color, size) { + ctx.moveTo(x, y); + ctx.beginPath(); + ctx.arc(x, y, size / 2, 0, 2 * Math.PI, false); + ctx.closePath(); + ctx.fillStyle = color; + ctx.fill(); + }; + + charts.line = { + + interactionPoints: allItems, + + setup: function (area) { + + if (area) { + canvasContain.addClass('visualize-area'); + } else { + canvasContain.addClass('visualize-line'); + } + + //write X labels + var xlabelsUL = $('').width(canvas.width()).height(canvas.height()).insertBefore(canvas); + + if (!o.customXLabels) { + xInterval = (canvas.width() - 2 * o.lineMargin) / (xLabels.length - 1); + $.each(xLabels, function (i) { + var thisLi = $('
  • ' + this + '
  • ').prepend('').css('left', o.lineMargin + xInterval * i).appendTo(xlabelsUL); + var label = thisLi.find('span:not(.line)'); + var leftOffset = label.width() / -2; + if (i == 0) { + leftOffset = -20; + } else if (i == xLabels.length - 1) { + leftOffset = -label.width() + 20; + } + label.css('margin-left', leftOffset).addClass('label'); + }); + } else { + o.customXLabels(tableData, xlabelsUL); + } + + //write Y labels + var liBottom = (canvas.height() - 2 * o.lineMargin) / (yLabels.length - 1); + var ylabelsUL = $('').width(canvas.width()).height(canvas.height()) + // .css('margin-top',-o.lineMargin) + .insertBefore(scroller); + + $.each(yLabels, function (i) { + var value = Math.floor(this); + var posB = (value - bottomValue) * yScale + o.lineMargin; + if (posB >= o.height - 1 || posB < 0) { + return; + } + var thisLi = $('
  • ' + value + '
  • ').css('bottom', posB); + if (Math.abs(posB) < o.height - 1) { + thisLi.prepend(''); + } + thisLi.prependTo(ylabelsUL); + + var label = thisLi.find('span:not(.line)'); + var topOffset = label.height() / -2; + if (!o.lineMargin) { + if (i == 0) { + topOffset = -label.height(); + } else if (i == yLabels.length - 1) { + topOffset = 0; + } + } + label.css('margin-top', topOffset).addClass('label'); + }); + + //start from the bottom left + ctx.translate(zeroLocX, zeroLocY); + + charts.line.draw(area); + + }, + + draw: function (area) { + // prevent drawing on top of previous draw + ctx.clearRect(-zeroLocX, -zeroLocY, o.width, o.height); + // Calculate each point properties before hand + var integer; + $.each(dataGroups, function (i, row) { + integer = o.lineMargin; // the current offset + $.each(row.points, function (j, point) { + if (o.xLabelParser) { + point.canvasCords = [(xLabels[j] - zeroLocX) * xScale - xBottomValue, -(point.value * yScale)]; + } else { + point.canvasCords = [integer, -(point.value * yScale)]; + } + + if (o.lineDots) { + point.dotSize = o.dotSize || o.lineWeight * Math.PI; + point.dotInnerSize = o.dotInnerSize || o.lineWeight * Math.PI / 2; + if (o.lineDots == 'double') { + point.innerColor = o.dotInnerColor; + } + } + integer += xInterval; + }); + }); + // fire custom event so we can enable rich interaction + self.trigger('vizualizeBeforeDraw', { + options: o, + table: self, + canvasContain: canvasContain, + tableData: tableData + }); + // draw lines and areas + $.each(dataGroups, function (h) { + // Draw lines + ctx.beginPath(); + ctx.lineWidth = o.lineWeight; + ctx.lineJoin = 'round'; + $.each(this.points, function (g) { + var loc = this.canvasCords; + if (g == 0) { + ctx.moveTo(loc[0], loc[1]); + } + ctx.lineTo(loc[0], loc[1]); + }); + ctx.strokeStyle = this.color; + ctx.stroke(); + // Draw fills + if (area) { + var integer = this.points[this.points.length - 1].canvasCords[0]; + if (isFinite(integer)) ctx.lineTo(integer, 0); + ctx.lineTo(o.lineMargin, 0); + ctx.closePath(); + ctx.fillStyle = this.color; + ctx.globalAlpha = .3; + ctx.fill(); + ctx.globalAlpha = 1.0; + } else { + ctx.closePath(); + } + }); + // draw points + if (o.lineDots) { + $.each(dataGroups, function (h) { + $.each(this.points, function (g) { + drawPoint(ctx, this.canvasCords[0], this.canvasCords[1], this.color, this.dotSize); + if (o.lineDots === 'double') { + drawPoint(ctx, this.canvasCords[0], this.canvasCords[1], this.innerColor, this.dotInnerSize); + } + }); + }); + } + + } + }; + + })(); + + charts.area = { + setup: function () { + charts.line.setup(true); + }, + draw: charts.line.draw + }; + + (function () { + + var horizontal, bottomLabels; + + charts.bar = { + setup: function () { + /** + * We can draw horizontal or vertical bars depending on the + * value of the 'barDirection' option (which may be 'vertical' or + * 'horizontal'). + */ + + horizontal = (o.barDirection == 'horizontal'); + + canvasContain.addClass('visualize-bar'); + + /** + * Write labels along the bottom of the chart. If we're drawing + * horizontal bars, these will be the yLabels, otherwise they + * will be the xLabels. The positioning also varies slightly: + * yLabels are values, hence they will span the whole width of + * the canvas, whereas xLabels are supposed to line up with the + * bars. + */ + bottomLabels = horizontal ? yLabels : xLabels; + + var xInterval = canvas.width() / (bottomLabels.length - (horizontal ? 1 : 0)); + + var xlabelsUL = $('').width(canvas.width()).height(canvas.height()).insertBefore(canvas); + + $.each(bottomLabels, function (i) { + var thisLi = $('
  • ' + this + '
  • ').prepend('').css('left', xInterval * i).width(xInterval).appendTo(xlabelsUL); + + if (horizontal) { + var label = thisLi.find('span.label'); + label.css("margin-left", -label.width() / 2); + } + }); + + /** + * Write labels along the left of the chart. Follows the same idea + * as the bottom labels. + */ + var leftLabels = horizontal ? xLabels : yLabels; + var liBottom = canvas.height() / (leftLabels.length - (horizontal ? 0 : 1)); + + var ylabelsUL = $('
      ').width(canvas.width()).height(canvas.height()).insertBefore(canvas); + + $.each(leftLabels, function (i) { + var thisLi = $('
    • ' + this + '
    • ').prependTo(ylabelsUL); + + var label = thisLi.find('span:not(.line)').addClass('label'); + + if (horizontal) { + /** + * For left labels, we want to vertically align the text + * to the middle of its container, but we don't know how + * many lines of text we will have, since the labels could + * be very long. + * + * So we set a min-height of liBottom, and a max-height + * of liBottom + 1, so we can then check the label's actual + * height to determine if it spans one line or more lines. + */ + label.css({ + 'min-height': liBottom, + 'max-height': liBottom + 1, + 'vertical-align': 'middle' + }); + thisLi.css({ + 'top': liBottom * i, + 'min-height': liBottom + }); + + var r = label[0].getClientRects()[0]; + if (r.bottom - r.top == liBottom) { +/* This means we have only one line of text; hence + * we can centre the text vertically by setting the line-height, + * as described at: + * http://www.ampsoft.net/webdesign-l/vertical-aligned-nav-list.html + * + * (Although firefox has .height on the rectangle, IE doesn't, + * so we use r.bottom - r.top rather than r.height.) + */ + label.css('line-height', parseInt(liBottom) + 'px'); + } else { +/* + * If there is more than one line of text, then we shouldn't + * touch the line height, but we should make sure the text + * doesn't overflow the container. + */ + label.css("overflow", "hidden"); + } + } else { + thisLi.css('bottom', liBottom * i).prepend(''); + label.css('margin-top', -label.height() / 2) + } + }); + + charts.bar.draw(); + + }, + + draw: function () { + // Draw bars + if (horizontal) { + // for horizontal, keep the same code, but rotate everything 90 degrees + // clockwise. + ctx.rotate(Math.PI / 2); + } else { + // for vertical, translate to the top left corner. + ctx.translate(0, zeroLocY); + } + + // Don't attempt to draw anything if all the values are zero, + // otherwise we will get weird exceptions from the canvas methods. + if (totalYRange <= 0) return; + + var yScale = (horizontal ? canvas.width() : canvas.height()) / totalYRange; + var barWidth = horizontal ? (canvas.height() / xLabels.length) : (canvas.width() / (bottomLabels.length)); + var linewidth = (barWidth - o.barGroupMargin * 2) / dataGroups.length; + + for (var h = 0; h < dataGroups.length; h++) { + ctx.beginPath(); + + var strokeWidth = linewidth - (o.barMargin * 2); + ctx.lineWidth = strokeWidth; + var points = dataGroups[h].points; + var integer = 0; + for (var i = 0; i < points.length; i++) { + // If the last value is zero, IE will go nuts and not draw anything, + // so don't try to draw zero values at all. + if (points[i].value != 0) { + var xVal = (integer - o.barGroupMargin) + (h * linewidth) + linewidth / 2; + xVal += o.barGroupMargin * 2; + ctx.moveTo(xVal, 0); + ctx.lineTo(xVal, Math.round(-points[i].value * yScale)); + } + integer += barWidth; + } + ctx.strokeStyle = dataGroups[h].color; + ctx.stroke(); + ctx.closePath(); + } + + } + }; + + })(); + + //create new canvas, set w&h attrs (not inline styles) + var canvasNode = document.createElement("canvas"); + var canvas = $(canvasNode).attr({ + 'height': o.height, + 'width': o.width + }); + + //get title for chart + var title = o.title || self.find('caption').text(); + + //create canvas wrapper div, set inline w&h, append + var canvasContain = (container || $('
      ')).height(o.height).width(o.width); + + var scroller = $('
      ').appendTo(canvasContain).append(canvas); + + //title/key container + if (o.appendTitle || o.appendKey) { + var infoContain = $('
      ').appendTo(canvasContain); + } + + //append title + if (o.appendTitle) { + $('
      ' + title + '
      ').appendTo(infoContain); + } + + + //append key + if (o.appendKey) { + var newKey = $('
        '); + $.each(yAllLabels, function (i, label) { + $('
      • ' + label + '
      • ').appendTo(newKey); + }); + newKey.appendTo(infoContain); + }; + + // init interaction + if (o.interaction) { + // sets the canvas to track interaction + // IE needs one div on top of the canvas since the VML shapes prevent mousemove from triggering correctly. + // Pie charts needs tracker because labels goes on top of the canvas and also messes up with mousemove + var tracker = $('
        ').css({ + 'height': o.height + 'px', + 'width': o.width + 'px', + 'position': 'relative', + 'z-index': 200 + }).insertAfter(canvas); + + var triggerInteraction = function (overOut, data) { + var data = $.extend({ + canvasContain: canvasContain, + tableData: tableData + }, data); + self.trigger('vizualize' + overOut, data); + }; + + var over = false, + last = false, + started = false; + tracker.mousemove(function (e) { + var x, y, x1, y1, data, dist, i, current, selector, zLabel, elem, color, minDist, found, ev = e.originalEvent; + + // get mouse position relative to the tracker/canvas + x = ev.layerX || ev.offsetX || 0; + y = ev.layerY || ev.offsetY || 0; + + found = false; + minDist = started ? 30000 : (o.type == 'pie' ? (Math.round(canvas.height() / 2) - o.pieMargin) / 3 : o.lineWeight * 4); + // iterate datagroups to find points with matching + $.each(charts[o.type].interactionPoints, function (i, current) { + x1 = current.canvasCords[0] + zeroLocX; + y1 = current.canvasCords[1] + (o.type == "pie" ? 0 : zeroLocY); + dist = Math.sqrt((x1 - x) * (x1 - x) + (y1 - y) * (y1 - y)); + if (dist < minDist) { + found = current; + minDist = dist; + } + }); + + if (o.multiHover && found) { + x = found.canvasCords[0] + zeroLocX; + y = found.canvasCords[1] + (o.type == "pie" ? 0 : zeroLocY); + found = [found]; + $.each(charts[o.type].interactionPoints, function (i, current) { + if (current == found[0]) { + return; + } + x1 = current.canvasCords[0] + zeroLocX; + y1 = current.canvasCords[1] + zeroLocY; + dist = Math.sqrt((x1 - x) * (x1 - x) + (y1 - y) * (y1 - y)); + if (dist <= o.multiHover) { + found.push(current); + } + }); + } + // trigger over and out only when state changes, instead of on every mousemove + over = found; + if (over != last) { + if (over) { + if (last) { + triggerInteraction('Out', { + point: last + }); + } + triggerInteraction('Over', { + point: over + }); + last = over; + } + if (last && !over) { + triggerInteraction('Out', { + point: last + }); + last = false; + } + started = true; + } + }); + tracker.mouseleave(function () { + triggerInteraction('Out', { + point: last, + mouseOutGraph: true + }); + over = (last = false); + }); + } + + //append new canvas to page + if (!container) { + canvasContain.insertAfter(this); + } + if (typeof (G_vmlCanvasManager) != 'undefined') { + G_vmlCanvasManager.init(); + G_vmlCanvasManager.initElement(canvas[0]); + } + + //set up the drawing board + var ctx = canvas[0].getContext('2d'); + + // Scroll graphs + scroller.scrollLeft(o.width - scroller.width()); + + // init plugins + $.each($.visualizePlugins, function (i, plugin) { + plugin.call(self, o, tableData); + }); + + //create chart + charts[o.type].setup(); + + if (!container) { + //add event for updating + self.bind('visualizeRefresh', function () { + self.visualize(o, $(this).empty()); + }); + //add event for redraw + self.bind('visualizeRedraw', function () { + charts[o.type].draw(); + }); + } + }).next(); //returns canvas(es) + }; + // create array for plugins. if you wish to make a plugin, + // just push your init funcion into this array + $.visualizePlugins = []; + +})(jQuery); diff --git a/public/templates/mmcFE/master.tpl b/public/templates/mmcFE/master.tpl index 3df30667..6348c119 100644 --- a/public/templates/mmcFE/master.tpl +++ b/public/templates/mmcFE/master.tpl @@ -9,6 +9,8 @@ + + diff --git a/public/templates/mmcFE/statistics/pool/authenticated.tpl b/public/templates/mmcFE/statistics/pool/authenticated.tpl index f278fb04..a92afd67 100644 --- a/public/templates/mmcFE/statistics/pool/authenticated.tpl +++ b/public/templates/mmcFE/statistics/pool/authenticated.tpl @@ -1,12 +1,14 @@ {include file="global/block_header.tpl" BLOCK_HEADER="Pool Statistics" BLOCK_STYLE="clear:none;"} -{include file="global/block_header.tpl" BLOCK_HEADER="Top 15 Hashrates" ALIGN="left" BUTTONS=array(More,Less)} +{include file="global/block_header.tpl" BLOCK_HEADER="Top Contributers"}
        - +
        + + @@ -17,35 +19,18 @@ + + {/section}
        Rank User Name KH/sSharesShares/s Ł/Day (est)
        {$rank++} {$TOPHASHRATES[hashrate].account} {$TOPHASHRATES[hashrate].hashrate|number_format}{$TOPHASHRATES[hashrate].shares|number_format}{math equation="round(shares / 600,3)" shares=$TOPHASHRATES[hashrate].shares} {math equation="round(reward / ( diff * pow(2,32) / ( hashrate * 1000 ) / 3600 / 24),3)" diff=$DIFFICULTY reward=$REWARD hashrate=$TOPHASHRATES[hashrate].hashrate}
        +
        {include file="global/block_footer.tpl"} -{include file="global/block_header.tpl" BLOCK_HEADER="Top 15 Contributers" ALIGN="right" BUTTONS=array(More,Less)} -
        - - - - - -{assign var=rank value=1} -{section contributor $CONTRIBUTORS} - - - - - -{/section} - -
        RankUser NameShares
        {$rank++}{$CONTRIBUTORS[contributor].account}{$CONTRIBUTORS[contributor].shares|number_format}
        -
        -{include file="global/block_footer.tpl"} - -{include file="global/block_header.tpl" BLOCK_HEADER="Server Stats" BLOCK_STYLE="clear:all;" STYLE="padding-left:5px;padding-right:5px;" BUTTONS=array(More)} +{include file="global/block_header.tpl" BLOCK_HEADER="Server Stats" BLOCK_STYLE="clear:all;" STYLE="padding-left:5px;padding-right:5px;"} diff --git a/public/templates/mmcFE/statistics/user/default.tpl b/public/templates/mmcFE/statistics/user/default.tpl new file mode 100644 index 00000000..ffc5e8c8 --- /dev/null +++ b/public/templates/mmcFE/statistics/user/default.tpl @@ -0,0 +1,23 @@ +{include file="global/block_header.tpl" BLOCK_HEADER="Your Average Hourly Hash Rate" BUTTONS=array(mine,pool,both)} +
        +
        + + + + +{section hashrate $YOURHASHRATES} + +{/section} + + + + + +{section hashrate $YOURHASHRATES} + +{/section} + + +
        Your Hashrate 
        {$YOURHASHRATES[hashrate].hour}
        {$GLOBAL.USERDATA.username}{$YOURHASHRATES[hashrate].hashrate|number_format}
        +
        +{include file="global/block_footer.tpl"}