diff --git a/cronjobs/blockupdate.php b/cronjobs/blockupdate.php
old mode 100644
new mode 100755
index 10f074d7..877471a8
--- a/cronjobs/blockupdate.php
+++ b/cronjobs/blockupdate.php
@@ -1,3 +1,4 @@
+#!/usr/bin/php
sError;
}
+ public function getTableName() {
+ return $this->table;
+ }
public function getLast() {
$stmt = $this->mysqli->prepare("SELECT * FROM $this->table ORDER BY height DESC LIMIT 1");
@@ -108,7 +111,7 @@ class Block {
$stmt->close();
return true;
}
- return false;
+ return false;
}
public function setFinder($block_id, $account_id=NULL) {
diff --git a/public/include/classes/statistics.class.php b/public/include/classes/statistics.class.php
index d409c131..0681fcce 100644
--- a/public/include/classes/statistics.class.php
+++ b/public/include/classes/statistics.class.php
@@ -8,12 +8,13 @@ class Statistics {
private $sError = '';
private $table = 'statistics_shares';
- public function __construct($debug, $mysqli, $config, $share, $user) {
+ public function __construct($debug, $mysqli, $config, $share, $user, $block) {
$this->debug = $debug;
$this->mysqli = $mysqli;
$this->share = $share;
$this->config = $config;
$this->user = $user;
+ $this->block = $block;
$this->debug->append("Instantiated Share class", 2);
}
@@ -34,6 +35,19 @@ class Statistics {
return true;
}
+ public function getBlocksFound($limit=10) {
+ $stmt = $this->mysqli->prepare("
+ SELECT b.*, a.username as finder
+ FROM " . $this->block->getTableName() . " AS b
+ LEFT JOIN accounts AS a
+ ON b.account_id = a.id
+ ORDER BY height DESC LIMIT ?");
+ if ($this->checkStmt($stmt) && $stmt->bind_param("i", $limit) && $stmt->execute() && $result = $stmt->get_result())
+ return $result->fetch_all(MYSQLI_ASSOC);
+ // Catchall
+ $this->debug->append("Failed to find blocks:" . $this->mysqli->error);
+ return false;
+ }
public function updateShareStatistics($aStats, $iBlockId) {
$stmt = $this->mysqli->prepare("INSERT INTO $this->table (account_id, valid, invalid, block_id) VALUES (?, ?, ?, ?)");
if ($this->checkStmt($stmt) && $stmt->bind_param('iiii', $aStats['id'], $aStats['valid'], $aStats['invalid'], $iBlockId) && $stmt->execute()) return true;
@@ -76,23 +90,50 @@ class Statistics {
( SELECT IFNULL(count(id), 0)
FROM " . $this->share->getTableName() . "
WHERE UNIX_TIMESTAMP(time) >IFNULL((SELECT MAX(time) FROM blocks),0)
- AND our_result = 'Y' ) as valid,
+ AND our_result = 'Y' ) as valid,
( SELECT IFNULL(count(id), 0)
FROM " . $this->share->getTableName() . "
WHERE UNIX_TIMESTAMP(time) >IFNULL((SELECT MAX(time) FROM blocks),0)
- AND our_result = 'N' ) as invalid");
+ AND our_result = 'N' ) as invalid");
if ( $this->checkStmt($stmt) && $stmt->execute() && $result = $stmt->get_result() ) return $result->fetch_assoc();
// Catchall
$this->debug->append("Failed to fetch round shares: " . $this->mysqli->error);
return false;
}
+ public function getUserShares($account_id) {
+ $stmt = $this->mysqli->prepare("
+ SELECT
+ (
+ SELECT COUNT(s.id)
+ FROM " . $this->share->getTableName() . " AS s,
+ " . $this->user->getTableName() . " AS u
+ WHERE u.username = SUBSTRING_INDEX( s.username, '.', 1 )
+ AND UNIX_TIMESTAMP(s.time) >IFNULL((SELECT MAX(b.time) FROM blocks AS b),0)
+ AND our_result = 'Y'
+ AND u.id = ?
+ ) AS valid,
+ (
+ SELECT COUNT(s.id)
+ FROM " . $this->share->getTableName() . " AS s,
+ " . $this->user->getTableName() . " AS u
+ WHERE u.username = SUBSTRING_INDEX( s.username, '.', 1 )
+ AND UNIX_TIMESTAMP(s.time) >IFNULL((SELECT MAX(b.time) FROM blocks AS b),0)
+ AND our_result = 'N'
+ AND u.id = ?
+ ) AS invalid");
+ if ($stmt && $stmt->bind_param("ii", $account_id, $account_id) && $stmt->execute() && $result = $stmt->get_result()) return $result->fetch_assoc();
+ // Catchall
+ $this->debug->append("Unable to fetch user round shares: " . $this->mysqli->error);
+ return false;
+ }
+
public function getUserHashrate($account_id) {
$stmt = $this->mysqli->prepare("
- SELECT ROUND(COUNT(s.id) * POW(2,21)/600/1000) AS hashrate
+ SELECT ROUND(COUNT(s.id) * POW(2, " . $this->config['difficulty'] . ")/600/1000) AS hashrate
FROM " . $this->share->getTableName() . " AS s,
- " . $this->user->getTableName() . " AS u
- WHERE u.username = SUBSTRING_INDEX( s.username, '.', 1 )
+ " . $this->user->getTableName() . " AS u
+ WHERE u.username = SUBSTRING_INDEX( s.username, '.', 1 )
AND s.time > DATE_SUB(now(), INTERVAL 10 MINUTE)
AND u.id = ?");
if ($this->checkStmt($stmt) && $stmt->bind_param("i", $account_id) && $stmt->execute() && $result = $stmt->get_result() ) return $result->fetch_object()->hashrate;
@@ -105,8 +146,8 @@ class Statistics {
$stmt = $this->mysqli->prepare("
SELECT ROUND(COUNT(s.id) * POW(2,21)/600/1000) AS hashrate
FROM " . $this->share->getTableName() . " AS s,
- " . $this->user->getTableName() . " AS u
- WHERE u.username = SUBSTRING_INDEX( s.username, '.', 1 )
+ " . $this->user->getTableName() . " AS u
+ WHERE u.username = SUBSTRING_INDEX( s.username, '.', 1 )
AND s.time > DATE_SUB(now(), INTERVAL 10 MINUTE)
AND u.id = ?");
if ($this->checkStmt($stmt) && $stmt->bind_param("i", $account_id) && $stmt->execute() && $result = $stmt->get_result() ) return $result->fetch_object()->hashrate;
@@ -114,6 +155,58 @@ class Statistics {
$this->debug->append("Failed to fetch hashrate: " . $this->mysqli->error);
return false;
}
-}
-$statistics = new Statistics($debug, $mysqli, $config, $share, $user);
+ public function getTopContributors($limit=15) {
+ $stmt = $this->mysqli->prepare("
+ SELECT
+ ROUND(COUNT(id) / 60 / 10, 2) AS sharesps,
+ ROUND(COUNT(id) * POW(2," . $this->config['difficulty'] . ")/600/1000,2) AS hashrate,
+ SUBSTRING_INDEX( username, '.', 1 ) AS account
+ FROM " . $this->share->getTableName() . "
+ WHERE time > DATE_SUB(now(), INTERVAL 10 MINUTE)
+ GROUP BY account
+ ORDER BY hashrate DESC LIMIT ?");
+ if ($this->checkStmt($stmt) && $stmt->bind_param("i", $limit) && $stmt->execute() && $hashrates= $stmt->get_result()) {
+ $aHashData = $hashrates->fetch_all(MYSQLI_ASSOC);
+ $stmt->close();
+ } else {
+ return false;
+ }
+ foreach ($aHashData as $key => $aData) {
+ $stmt = $this->mysqli->prepare("SELECT COUNT(id) FROM " . $this->share->getTableName() . " WHERE SUBSTRING_INDEX( username , '.', 1 ) = ?");
+ if ($stmt->bind_param("s", $aData['username']) && $stmt->execute() && $result = $stmt->get_result()) {
+ $aHashData[$key]['shares'] = $this->getUserShares($this->user->getUserId($aData['account']))['valid'];
+ } else {
+ continue;
+ }
+ }
+ return $aHashData;
+ }
+
+ 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, $block);
diff --git a/public/include/classes/user.class.php b/public/include/classes/user.class.php
index fe358314..f9b8e5e3 100644
--- a/public/include/classes/user.class.php
+++ b/public/include/classes/user.class.php
@@ -27,11 +27,11 @@ class User {
}
public function getUserName($id) {
- return $this->getSingle($id, 'username');
+ return $this->getSingle($id, 'username', 'id');
}
public function getUserId($username) {
- return $this->getSingle($username, 'id', 'username');
+ return $this->getSingle($username, 'id', 'username', 's');
}
public function checkLogin($username, $password) {
@@ -55,10 +55,10 @@ class User {
return $pin_hash === $row_pin;
}
- private function getSingle($value, $search='id', $field='id') {
+ private function getSingle($value, $search='id', $field='id', $type="i") {
$stmt = $this->mysqli->prepare("SELECT $search FROM $this->table WHERE $field = ? LIMIT 1");
if ($this->checkStmt($stmt)) {
- $stmt->bind_param('i', $value);
+ $stmt->bind_param($type, $value);
$stmt->execute();
$stmt->bind_result($retval);
$stmt->fetch();
@@ -69,7 +69,7 @@ class User {
}
public function getCoinAddress($userID) {
- return $this->getSingle($userID, 'coin_address');
+ return $this->getSingle($userID, 'coin_address', 'id', 's');
}
private function updateSingle($userID, $field, $table) {
@@ -171,21 +171,7 @@ class User {
$stmt = $this->mysqli->prepare("
SELECT
id, username, pin, pass, admin,
- IFNULL(donate_percent, '0') as donate_percent, coin_address, ap_threshold,
- (
- SELECT COUNT(id)
- FROM shares
- WHERE $this->table.username = SUBSTRING_INDEX( `username` , '.', 1 )
- AND UNIX_TIMESTAMP(time) >IFNULL((SELECT MAX(time) FROM blocks),0)
- AND our_result = 'Y'
- ) AS valid,
- (
- SELECT COUNT(id)
- FROM shares
- WHERE $this->table.username = SUBSTRING_INDEX( `username` , '.', 1 )
- AND UNIX_TIMESTAMP(time) >IFNULL((SELECT MAX(time) FROM blocks),0)
- AND our_result = 'N'
- ) AS invalid
+ IFNULL(donate_percent, '0') as donate_percent, coin_address, ap_threshold
FROM $this->table
WHERE id = ? LIMIT 0,1");
echo $this->mysqli->error;
diff --git a/public/include/config/global.inc.dist.php b/public/include/config/global.inc.dist.php
index 92d21d8f..fead5b6c 100644
--- a/public/include/config/global.inc.dist.php
+++ b/public/include/config/global.inc.dist.php
@@ -16,11 +16,15 @@ define('CLASS_DIR', INCLUDE_DIR . '/classes');
define('PAGES_DIR', INCLUDE_DIR . '/pages');
// Set debugging level for our debug class
-define('DEBUG', 5);
+define('DEBUG', 0);
-define('SALT', 'LJKEHFuhgu7%&¤Hg783tr7gf¤%¤fyegfredfoGHYFGYe(%/(&%6');
+define('SALT', 'PLEASEMAKEMESOMETHINGRANDOM');
$config = array(
+ 'website' => array(
+ 'name' => 'The Pool',
+ 'slogan' => 'Resistance is futile',
+ ),
'difficulty' => '31', // Target difficulty for this pool
'reward' => '50', // Reward for finding blocks
'confirmations' => '120', // Confirmations per block found to credit transactions
diff --git a/public/include/pages/statistics/pool.inc.php b/public/include/pages/statistics/pool.inc.php
index bc12c3af..005e167d 100644
--- a/public/include/pages/statistics/pool.inc.php
+++ b/public/include/pages/statistics/pool.inc.php
@@ -21,45 +21,15 @@ if ($bitcoin->can_connect() === true){
}
if (!$aHashData = $memcache->get('aHashData')) {
- $debug->append('Hashrates expired in memcache');
- // Top 15 hashrate list
- $stmt = $mysqli->prepare("SELECT
- 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");
- $stmt->execute();
- $hashrates= $stmt->get_result();
- $aHashData = $hashrates->fetch_all(MYSQLI_ASSOC);
- $stmt->close();
+ $debug->append('STA Fetching Hashrates from database');
+ $aHashData = $statistics->getTopContributors();
$memcache->set('aHashData', $aHashData, 60);
+ $debug->append('END Fetching Hashrates from database');
}
-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);
-}
-
-// Grab the last block found
-$stmt = $mysqli->prepare("SELECT * FROM blocks ORDER BY height DESC LIMIT 1");
-$stmt->execute();
-$blocks = $stmt->get_result();
-$aBlockData = $blocks->fetch_array();
-$stmt->close();
-
// Grab the last 10 blocks found
-$stmt = $mysqli->prepare("SELECT b.*, a.username as finder FROM blocks AS b LEFT JOIN accounts AS a ON b.account_id = a.id ORDER BY height DESC LIMIT 10");
-$stmt->execute();
-$blocksfound = $stmt->get_result();
-$aBlocksFoundData = $blocksfound->fetch_all(MYSQLI_ASSOC);
-$stmt->close();
+$aBlocksFoundData = $statistics->getBlocksFound(10);
+$aBlockData = $aBlocksFoundData[0];
// Estimated time to find the next block
if (!$iCurrentPoolHashrate = $memcache->get('iCurrentPoolHashrate')) {
@@ -81,7 +51,6 @@ if (!empty($aBlockData)) {
// Propagate content our template
$smarty->assign("ESTTIME", $iEstTime);
$smarty->assign("TIMESINCELAST", $dTimeSinceLast);
-$smarty->assign("CONTRIBUTORS", $aContributerData);
$smarty->assign("BLOCKSFOUND", $aBlocksFoundData);
$smarty->assign("TOPHASHRATES", $aHashData);
$smarty->assign("CURRENTBLOCK", $iBlock);
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/include/smarty_globals.inc.php b/public/include/smarty_globals.inc.php
index 218e6af4..060aba83 100644
--- a/public/include/smarty_globals.inc.php
+++ b/public/include/smarty_globals.inc.php
@@ -9,39 +9,40 @@ $debug->append('Global smarty variables', 3);
// Store some stuff in memcache prior to assigning it to Smarty
if (!$aRoundShares = $memcache->get('aRoundShares')) {
- $debug->append('Fetching aRoundShares from database');
+ $debug->append('STA Fetching aRoundShares from database');
$aRoundShares = $statistics->getRoundShares();
- $memcache->set('aRoundShares', $aRoundShares, 60);
+ $debug->append('END Fetching aRoundShares from database');
+ $memcache->set('aRoundShares', $aRoundShares, 90);
}
if (!$iCurrentActiveWorkers = $memcache->get('iCurrentActiveWorkers')) {
- $debug->append('Fetching iCurrentActiveWorkers from database');
+ $debug->append('STA Fetching iCurrentActiveWorkers from database');
$iCurrentActiveWorkers = $worker->getCountAllActiveWorkers();
- $memcache->set('iCurrentActiveWorkers', $iCurrentActiveWorkers, 60);
+ $debug->append('END Fetching iCurrentActiveWorkers from database');
+ $memcache->set('iCurrentActiveWorkers', $iCurrentActiveWorkers, 80);
}
if (!$iCurrentPoolHashrate = $memcache->get('iCurrentPoolHashrate')) {
- $debug->append('Fetching iCurrentPoolHashrate from database');
+ $debug->append('STA Fetching iCurrentPoolHashrate from database');
$iCurrentPoolHashrate = $statistics->getCurrentHashrate();
- $memcache->set('iCurrentPoolHashrate', $iCurrentPoolHashrate, 60);
+ $debug->append('END Fetching iCurrentPoolHashrate from database');
+ $memcache->set('iCurrentPoolHashrate', $iCurrentPoolHashrate, 90);
}
if (!$iCurrentPoolShareRate = $memcache->get('iCurrentPoolShareRate')) {
- $debug->append('Fetching iCurrentPoolShareRate from database');
+ $debug->append('STA Fetching iCurrentPoolShareRate from database');
$iCurrentPoolShareRate = $statistics->getCurrentShareRate();
- $memcache->set('iCurrentPoolShareRate', $iCurrentPoolShareRate, 60);
+ $debug->append('END Fetching iCurrentPoolShareRate from database');
+ $memcache->set('iCurrentPoolShareRate', $iCurrentPoolShareRate, 90);
}
$aGlobal = array(
- 'slogan' => $settings->getValue('slogan'),
- 'websitename' => $settings->getValue('websitename'),
- 'ltc_usd' => $settings->getValue('btcesell'),
+ 'slogan' => $config['website']['slogan'],
+ 'websitename' => $config['website']['name'],
'hashrate' => $iCurrentPoolHashrate,
'sharerate' => $iCurrentPoolShareRate,
'workers' => $iCurrentActiveWorkers,
'roundshares' => $aRoundShares,
- 'statstime' => $settings->getValue('statstime'),
- 'motd' => $settings->getValue('motd'),
'confirmations' => $config['confirmations'],
'reward' => $config['reward']
);
@@ -53,9 +54,18 @@ $aGlobal['userdata'] = $_SESSION['USERDATA']['id'] ? $user->getUserData($_SESSIO
$aGlobal['userdata']['balance'] = $transaction->getBalance($_SESSION['USERDATA']['id']);
// Other userdata that we can cache savely
+if (!$aGlobal['userdata']['shares'] = $memcache->get('global_' . $_SESSION['USERDATA']['id'] . '_shares')) {
+ $debug->append('STA Loading user shares from database');
+ $aGlobal['userdata']['shares'] = $statistics->getUserShares($_SESSION['USERDATA']['id']);
+ $debug->append('END Loading user shares from database');
+ $memcache->set('global_' . $_SESSION['USERDATA']['id'] . '_shares', $aGlobal['userdata']['shares'], 80);
+}
+
if (!$aGlobal['userdata']['hashrate'] = $memcache->get('global_' . $_SESSION['USERDATA']['id'] . '_hashrate') ) {
+ $debug->append('STA Loading user hashrate from database');
$aGlobal['userdata']['hashrate'] = $statistics->getUserHashrate($_SESSION['USERDATA']['id']);
- $memcache->set('global_' . $_SESSION['USERDATA']['id'] . '_hashrate', $aGlobal['userdata']['hashrate'], 60);
+ $debug->append('END Loading user hashrate from database');
+ $memcache->set('global_' . $_SESSION['USERDATA']['id'] . '_hashrate', $aGlobal['userdata']['hashrate'], 70);
}
// Make it available in Smarty
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/global/footer.tpl b/public/templates/mmcFE/global/footer.tpl
index 68e7c3e0..bf425d19 100644
--- a/public/templates/mmcFE/global/footer.tpl
+++ b/public/templates/mmcFE/global/footer.tpl
@@ -1,5 +1,5 @@
- Litecoin Pool using on litecoind, pushpoold
- Website based on mmcfe, overhauled by TheSerapher, available on GitHub
+ Litecoin Pool using litecoind, pushpoold
+ mmcfe-ng Website based on mmcfe, overhauled by TheSerapher, available on GitHub
LTC: Lge95QR2frp9y1wJufjUPCycVsg5gLJPW8
diff --git a/public/templates/mmcFE/global/header.tpl b/public/templates/mmcFE/global/header.tpl
index 4a7bc449..1f08a366 100644
--- a/public/templates/mmcFE/global/header.tpl
+++ b/public/templates/mmcFE/global/header.tpl
@@ -4,7 +4,6 @@
- | Ł/usd: {$GLOBAL.ltc_usd} |
Pool Hashrate: {$GLOBAL.hashrate / 1000} MH/s |
Pool Sharerate: {$GLOBAL.sharerate} Shares/s |
Pool Workers: {$GLOBAL.workers} |
diff --git a/public/templates/mmcFE/global/navigation.tpl b/public/templates/mmcFE/global/navigation.tpl
index 3267f0db..83400cae 100644
--- a/public/templates/mmcFE/global/navigation.tpl
+++ b/public/templates/mmcFE/global/navigation.tpl
@@ -10,7 +10,7 @@
{/if}
- {if $smarty.session.AUTHENTICATED == 1 && $GLOBAL.userdata.admin == 1}Admin Panel{/if}
+ {if $smarty.session.AUTHENTICATED == 1 && $GLOBAL.userdata.admin == 1}Admin Panel{/if}
Statistics