diff --git a/models/history.js b/models/history.js index 1f35315..c7f2a13 100644 --- a/models/history.js +++ b/models/history.js @@ -1,4 +1,5 @@ var _ = require('lodash'); +var d3 = require('d3'); var MAX_HISTORY = 1008; var MAX_PEER_PROPAGATION = 36; @@ -128,23 +129,34 @@ History.prototype.getBlockPropagation = function() { var propagation = []; - var sorted = _(this._items) - .sortByOrder('height', false) - .slice(0, MAX_BLOCK_PROPAGATION) - .reverse() - .forEach(function(n, key) + _.forEach(this._items, function(n, key) + { + _.forEach(n.propagTimes, function(p, i) { - _.forEach(n.propagTimes, function(p, i) - { - var prop = _.result(p, 'propagation', -1); + var prop = _.result(p, 'propagation', -1); - if(prop >= 0) - propagation.push(prop); - }); - }) - .value(); + if(prop >= 0) + propagation.push(prop); + }); + }); - return propagation; + var x = d3.scale.linear() + .domain([0, 20000]) + .interpolate(d3.interpolateRound); + + var data = d3.layout.histogram() + .frequency(false) + .bins(x.ticks(MAX_BINS)) + (propagation); + + var freqCum = 0; + var histo = data.map(function(val) { + freqCum += val.length; + var cumPercent = (freqCum / Math.max(1, propagation.length)); + return {x: val.x, dx: val.dx, y: val.y, frequency: val.length, cumulative: freqCum, cumpercent: cumPercent}; + }); + + return histo; } History.prototype.history = function() diff --git a/package.json b/package.json index 940fdaa..eea27c0 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "body-parser": "1.12.3", + "d3": "^3.5.5", "debug": "2.1.3", "express": "4.12.3", "geoip-lite": "1.1.6", diff --git a/public/css/style.css b/public/css/style.css index 1d0cb1d..08d8015 100644 --- a/public/css/style.css +++ b/public/css/style.css @@ -184,7 +184,7 @@ div.small-title-miner { .page-latency { position: absolute; - top: 400px; + top: 395px; left: 15px; } @@ -239,7 +239,7 @@ table td.peerPropagationChart { } .th-nodetype { - width: 200px; + width: 220px; } .th-latency { @@ -302,8 +302,7 @@ table td.peerPropagationChart { padding: 5px 0; } -.jqsfield .tooltip-arrow, -.d3-tip .tooltip-arrow { +.jqsfield .tooltip-arrow { position: absolute; bottom: 0; left: 50%; @@ -312,7 +311,8 @@ table td.peerPropagationChart { border-top-color: #fff; } -.datamaps-hoverover .tooltip-arrow { +.datamaps-hoverover .tooltip-arrow, +.d3-tip .tooltip-arrow { position: absolute; top: -5px; left: 0px; @@ -321,6 +321,11 @@ table td.peerPropagationChart { border-bottom-color: #fff; } +.d3-tip .tooltip-arrow { + top: 0px; + left: 50%; +} + .hoverinfo { position: relative; width: auto; diff --git a/public/js/controllers.js b/public/js/controllers.js index cde4b1c..b64c3c8 100644 --- a/public/js/controllers.js +++ b/public/js/controllers.js @@ -33,7 +33,7 @@ function StatsCtrl($scope, $filter, socket, _, toastr) { $scope.orderTable = function(predicate, reverse) { - if(predicate != $scope.predicate) + if(!_.isEqual(predicate, $scope.predicate)) { $scope.reverse = reverse; $scope.predicate = predicate; diff --git a/public/js/directives.js b/public/js/directives.js index a29a45c..d84adf2 100644 --- a/public/js/directives.js +++ b/public/js/directives.js @@ -121,18 +121,15 @@ angular.module('netStatsApp.directives', []). var tip = d3.tip() .attr('class', 'd3-tip') - .offset([-10, 0]) + .offset([10, 0]) + .direction('s') .html(function(d) { - return '
' + (d.x/1000) + 's - ' + ((d.x + d.dx)/1000) + 's: ' + Math.round(d.y * 100) + "%" + "
"; + return '
' + (d.x/1000) + 's - ' + ((d.x + d.dx)/1000) + 's
Percent: ' + Math.round(d.y * 100) + '%' + '
Frequency: ' + d.frequency + '
Cumulative: ' + Math.floor(d.cumpercent*100) + '%
'; }) scope.init = function() { - // Init data - var data = d3.layout.histogram() - .frequency(false) - .bins(x.ticks(TICKS)) - (scope.data); + var data = scope.data; // Adjust y axis y.domain([0, d3.max(data, function(d) { return d.y; })]); @@ -198,8 +195,6 @@ angular.module('netStatsApp.directives', []). .attr("d", line(data)); } - scope.init(); - scope.$watch('data', function() { scope.init(); }, true); diff --git a/public/js/filters.js b/public/js/filters.js index d5f42e7..2116985 100644 --- a/public/js/filters.js +++ b/public/js/filters.js @@ -42,6 +42,22 @@ angular.module('netStatsApp.filters', []) return (! mining ? 'icon-cancel' : 'icon-check'); }; }) +.filter('hashpowerClass', function() { + return function(mining) { + if(! mining) + return 'text-gray'; + + return 'text-success'; + }; +}) +.filter('hashrateFilter', function() { + return function(hashrate) { + if(typeof hashrate === 'undefined' || !hashrate) + return 0; + + return hashrate/1000; + } +}) .filter('nodeVersion', function($sce) { return function(version) { if(typeof version !== 'undefined') diff --git a/views/index.jade b/views/index.jade index 26bfa66..24638ca 100644 --- a/views/index.jade +++ b/views/index.jade @@ -116,6 +116,8 @@ block content i.icon-clock(data-toggle="tooltip", data-placement="top", title="Node latency", ng-click="orderTable(['-stats.active', 'stats.latency'], false)") th i.icon-mining(data-toggle="tooltip", data-placement="top", title="Is mining", ng-click="orderTable(['-stats.active', '-stats.mining'], false)") + th + i.icon-mining(data-toggle="tooltip", data-placement="top", title="Hashrate", ng-click="orderTable(['-stats.active', '-stats.mining', '-stats.hashrate'], false)") th i.icon-group(data-toggle="tooltip", data-placement="top", title="Peers", ng-click="orderTable(['-stats.active', '-stats.peers'], false)") th @@ -139,10 +141,12 @@ block content span.small #[ ]({{node.info.ip}}) td div.small(ng-bind-html="node.info.node | nodeVersion") - //- div.small {{node.info.os}}, {{node.info.os_v}} - td.small(class="{{ node.stats | latencyClass }}") {{node.stats | latencyFilter}} + td(class="{{ node.stats | latencyClass }}") + span.small {{node.stats | latencyFilter}} td(class="{{ node.stats.mining | miningClass : node.stats.active }}") i(class="{{ node.stats.mining | miningIconClass }}") + td(class="{{ node.stats.mining | hashpowerClass }}") + span.small {{node.stats.hashrate | hashrateFilter}} kH/s td(class="{{ node.stats.peers | peerClass : node.stats.active }}", style="padding-left: 11px;") {{node.stats.peers}} td(style="padding-left: 15px;") {{node.stats.pending}} td(class="{{ node.stats | blockClass : bestBlock }}") {{'#'}}{{ node.stats.block.number | number }}