Change timeline ui to infinite circular scroll

Infinite circular scroll bug fix.
Add timeline self execution time search field
This commit is contained in:
denz
2015-06-28 15:19:26 +09:00
parent d4cbd9cd8c
commit f768dfaa2e
6 changed files with 240 additions and 173 deletions
@@ -0,0 +1,167 @@
(function(window, $) {
'use strict';
var ROW_SPARE_COUNT = 8;
window.InfiniteCircularScroll = $.Class({
$init: function(options) {
this.option(options);
this._init();
this._initVar();
this._initEvent();
},
_init: function() {
this.$wrapper = this.option("wrapper");
this._threshold = this.option("elementHeight") * (ROW_SPARE_COUNT / 4); // 2 * elementHeight
this._previousTop = 0;
this._previousTime = -1;
this._selectedRow = -1;
},
_initVar: function() {
this._elementArray = [];
this._elementArrayStartIndex = 0;
this._elementArrayEndIndex = 0;
this._elementArraySize = 0;
this._previousTop = 0;
},
_initEvent: function() {
var self = this;
this.option("scroller").on("scroll", function(event) {
self._scrollEventHandler(event, $(this));
});
},
_calculateElementStartCount: function() {
var temp = parseInt( this._viewAreaHeight / this.option("elementHeight") ) + ROW_SPARE_COUNT;
this._elementArraySize = temp > this._source.length ? this._source.length : temp;
this._elementArrayEndIndex = this._elementArraySize - 1;
},
_initElementArray: function() {
this.$wrapper.empty();
for( var i = 0 ; i < this._elementArraySize ; i++ ) {
var $element = $(this.option("template"));
this._renderElement($element, i);
this._elementArray.push($element);
this.$wrapper.append($element);
}
},
_renderElement: function($element, index) {
this._renderFunc.call( this, $element, index, this._source[index] );
$element.attr("data-index", index).css("top", (index * this.option("elementHeight")) + "px" );
},
_getDataIndex: function(index) {
return parseInt(this._elementArray[index].attr("data-index"));
},
_getTopByIndex: function(index) {
return parseInt(this._elementArray[index].css("top"));
},
_getNextArrayIndex: function(index) {
return index + 1 >= this._elementArraySize ? 0 : index + 1;
},
_getPreviousArrayIndex: function(index) {
return ( index - 1 < 0 ? this._elementArraySize : index ) - 1;
},
_scrollEventHandler: function(event, $targetElement) {
var top = $targetElement.scrollTop();
var mTime = new Date().valueOf();
if ( top == this._previousTop ) return;
// if ( this._previousTime != -1 && ( mTime - this._previousTime ) < 16 ) return;
var bIsDown = top - this._previousTop > 0;
if ( bIsDown ) { // 아래로 스크롤 - top 값은 커진다.
this._scrollDown(top, this._getTopByIndex(this._elementArrayStartIndex));
} else { // 위로 스크롤 - top 값은 작아진다.
this._scrollUp(top, this._getTopByIndex(this._elementArrayEndIndex));
}
this._previousTop = top;
this._previousTime = mTime;
},
_scrollDown: function( top, firstElementTop ) {
var exceededDistance = top - firstElementTop - this._threshold;
if ( exceededDistance >= 0 ) {
var sourceLastIndex = this._getDataIndex(this._elementArrayEndIndex);
var nextIndex = this._elementArrayStartIndex;
var nextIndexStep = parseInt(exceededDistance / this.option("elementHeight"));
for( var i = 0 ; i < nextIndexStep ; i++ ) {
var sourceIndex = sourceLastIndex + i + 1;
if ( sourceIndex >= this._source.length ) {
break;
}
this._renderElement(this._elementArray[nextIndex], sourceIndex);
nextIndex = this._getNextArrayIndex(nextIndex);
}
this._elementArrayStartIndex = nextIndex;
this._elementArrayEndIndex = this._getPreviousArrayIndex(nextIndex);
}
},
_scrollUp: function( top, lastElementTop ) {
var exceededDistance = lastElementTop + this.option("elementHeight") - top - this._viewAreaHeight - this._threshold;
if ( exceededDistance >= 0 ) {
var sourceFirstIndex = this._getDataIndex(this._elementArrayStartIndex);
var previousIndex = this._elementArrayEndIndex;
var previousIndexStep = parseInt(exceededDistance / this.option("elementHeight"));
for( var i = 0 ; i < previousIndexStep ; i++ ) {
var sourceIndex = sourceFirstIndex - (i + 1);
if ( sourceIndex < 0 ) {
break;
}
this._renderElement(this._elementArray[previousIndex], sourceIndex);
previousIndex = this._getPreviousArrayIndex(previousIndex);
}
this._elementArrayEndIndex = previousIndex;
this._elementArrayStartIndex = this._getNextArrayIndex(previousIndex);
}
},
setSource: function( source ) {
this._source= source;
return this;
},
setViewAreaHeight: function( viewAreaHeight ) {
this._viewAreaHeight = viewAreaHeight;
return this;
},
setRenderFunc: function( fnRender ) {
this._renderFunc = fnRender;
return this;
},
reset: function() {
this._initVar();
this._calculateElementStartCount();
this._initElementArray();
return this;
},
getContentsAreaHeight: function() {
return this.option("elementHeight") * this._source.length;
},
destroy: function() {
this.$wrapper.parent().off("scroll");
},
resize: function( resizedViewAreaHeight ) {
var currentRow = parseInt(this.option("scroller").scrollTop() / this.option("elementHeight"));
this.setViewAreaHeight(resizedViewAreaHeight);
this.reset();
this.moveByRow( currentRow );
},
moveByRow: function( row ) { // from 0
var self = this;
setTimeout(function() {
self.option("scroller").scrollTop(row * self.option("elementHeight"));
},0);
},
searchRow: function( from, to, func) {
var resultRow = -1;
to = to == -1 ? this._source.length : to;
for( var i = from ; i < to ; i++ ) {
if ( func(this._source[i]) ) {
resultRow = i;
break;
}
}
return resultRow;
},
setSelectedRow: function(row, func) {
func.call(this, row);
this._selectedRow = row;
return this;
}
});
})(window, jQuery);
@@ -424,13 +424,13 @@
if ( row == -1 ) {
if ( index > 0 ) {
selectRow( searchRowByTime(time, 0) );
scope.$emit("transactionDetail.searchCallresult", "Loop" );
scope.$emit("transactionDetail.calltreeSearchCallResult", "Loop" );
} else {
scope.$emit("transactionDetail.searchCallresult", "No call took longer than {time}ms." );
scope.$emit("transactionDetail.calltreeSearchCallResult", "No call took longer than {time}ms." );
}
} else {
selectRow(row);
scope.$emit("transactionDetail.searchCallresult", "" );
scope.$emit("transactionDetail.calltreeSearchCallResult", "" );
}
});
searchRowByTime = function( time, index ) {
@@ -1,163 +1,6 @@
(function( $ ) {
'use strict';
function InfiniteCircularScroller(options) {
this.options = options || {};
this._init();
this._initVar();
this._initEvent();
};
InfiniteCircularScroller.prototype._init = function() {
this.options.initPlusCount = 8;
this.$wrapper = this.options.wrapper;
this._threshold = this.options.elementHeight * (this.options.initPlusCount / 4);
this._previousTop = 0;
};
InfiniteCircularScroller.prototype._initVar = function() {
this._elementArray = [];
this._elementArrayStartIndex = 0;
this._elementArrayEndIndex = 0;
this._elementArraySize = 0;
this._previousTop = 0;
};
InfiniteCircularScroller.prototype._initEvent = function() {
var self = this;
this.options.scroller.on("scroll", function(event) {
self._scrollEventHandler(event, $(this));
});
};
InfiniteCircularScroller.prototype._calculateElementStartCount = function() {
var temp = parseInt( this._viewAreaHeight / this.options.elementHeight ) + this.options.initPlusCount;
this._elementArraySize = temp > this._source.length ? this._source.length : temp;
this._elementArrayEndIndex = this._elementArraySize - 1;
};
InfiniteCircularScroller.prototype._initElementArray = function() {
this.$wrapper.empty();
for( var i = 0 ; i < this._elementArraySize ; i++ ) {
var $element = $(this.options.template);
this._renderElement($element, i);
this._elementArray.push($element);
this.$wrapper.append($element);
}
};
InfiniteCircularScroller.prototype._renderElement = function($element, index) {
this._renderFunc.call( this, $element, index, this._source[index] );
$element.attr("data-index", index).css("top", (index * this.options.elementHeight) + "px" );
};
InfiniteCircularScroller.prototype._getDataIndex = function(index) {
return parseInt(this._elementArray[index].attr("data-index"));
};
InfiniteCircularScroller.prototype._getTopByIndex = function(index) {
return parseInt(this._elementArray[index].css("top"));
};
InfiniteCircularScroller.prototype._getNextArrayIndex = function(index) {
return index + 1 >= this._elementArraySize ? 0 : index + 1;
};
InfiniteCircularScroller.prototype._getPreviousArrayIndex = function(index) {
return ( index - 1 < 0 ? this._elementArraySize : index ) - 1;
};
InfiniteCircularScroller.prototype._scrollEventHandler = function(event, $targetElement) {
var top = $targetElement.scrollTop();
if ( top == this._previousTop ) return;
if ( top < this._threshold ) return;
//if ( top - this._previousTop > this._viewAreaHeight ) 스크롤 이벤트 입력값이 화면 보다 크게 오는 경우
var bIsDown = top - this._previousTop > 0;
if ( bIsDown ) { // 아래로 스크롤 - top 값은 커진다.
this._scrollDown(top, this._getTopByIndex(this._elementArrayStartIndex));
} else { // 위로 스크롤 - top 값은 작아진다.
this._scrollUp(top, this._getTopByIndex(this._elementArrayEndIndex));
}
this._previousTop = top;
};
InfiniteCircularScroller.prototype._scrollDown = function( top, firstElementTop ) {
var exceededDistance = top - firstElementTop - this._threshold;
if ( exceededDistance >= 0 ) {
var sourceLastIndex = this._getDataIndex(this._elementArrayEndIndex);
var nextIndex = this._elementArrayStartIndex;
var nextIndexStep = parseInt(exceededDistance / this.options.elementHeight);
for( var i = 0 ; i < nextIndexStep ; i++ ) {
var sourceIndex = sourceLastIndex + i + 1;
if ( sourceIndex >= this._source.length ) {
break;
}
this._renderElement(this._elementArray[nextIndex], sourceIndex);
nextIndex = this._getNextArrayIndex(nextIndex);
}
this._elementArrayStartIndex = nextIndex;
this._elementArrayEndIndex = this._getPreviousArrayIndex(nextIndex);
}
};
InfiniteCircularScroller.prototype._scrollUp = function( top, lastElementTop ) {
var exceededDistance = lastElementTop + this.options.elementHeight - top - this._viewAreaHeight - this._threshold;
if ( exceededDistance >= 0 ) {
var sourceFirstIndex = this._getDataIndex(this._elementArrayStartIndex);
var previousIndex = this._elementArrayEndIndex;
var previousIndexStep = parseInt(exceededDistance / this.options.elementHeight);
for( var i = 0 ; i < previousIndexStep ; i++ ) {
var sourceIndex = sourceFirstIndex - (i + 1);
if ( sourceIndex < 0 ) {
break;
}
this._renderElement(this._elementArray[previousIndex], sourceIndex);
previousIndex = this._getPreviousArrayIndex(previousIndex);
}
this._elementArrayEndIndex = previousIndex;
this._elementArrayStartIndex = this._getNextArrayIndex(previousIndex);
}
};
InfiniteCircularScroller.prototype.setSource = function( source ) {
this._source= source;
return this;
};
InfiniteCircularScroller.prototype.setViewAreaHeight = function( viewAreaHeight ) {
this._viewAreaHeight = viewAreaHeight;
return this;
};
InfiniteCircularScroller.prototype.setRenderFunc = function( fnRender ) {
this._renderFunc = fnRender;
return this;
};
InfiniteCircularScroller.prototype.reset = function() {
this._initVar();
this._calculateElementStartCount();
this._initElementArray();
return this;
};
InfiniteCircularScroller.prototype.getContentsAreaHeight = function() {
return this.options.elementHeight * this._source.length;
};
InfiniteCircularScroller.prototype.destroy = function() {
this.$wrapper.parent().off("scroll");
};
InfiniteCircularScroller.prototype.resize = function( resizedViewAreaHeight ) {
var currentRow = parseInt(this.options.scroller.scrollTop() / this.options.elementHeight);
this.setViewAreaHeight(resizedViewAreaHeight);
this.reset();
this.moveByRow( currentRow );
};
InfiniteCircularScroller.prototype.moveByRow = function( row ) {
var self = this;
setTimeout(function() {
self.options.scroller.scrollTop(0);
},0);
};
InfiniteCircularScroller.prototype.moveByValue = function(from, func) {
for( var i = from ; i < this._source.length ; i++ ) {
if ( func(this._source[i]) ) {
this.moveByRow(i);
break;
}
}
};
/*
* icscroller.moveByValue( 0, function( elementData ) {
* return (elementData[scope.key.end] - elementData[scope.key.begin]) >= selfExecutionTime;
* });
*/
pinpointApp.directive('timelineDirective', function () {
return {
restrict: 'EA',
@@ -166,13 +9,13 @@
link: function postLink(scope, element, attrs) {
// define private variables of methods
var initialize, getColorByString, filterCallStacks, viewAreaHeight, initBarCount, renderCallStack;
var initialize, getColorByString, filterCallStacks, viewAreaHeight, initBarCount, renderCallStack, searchTime, searchStartIndex, searchSuccess;
var colorSet = [
"#66CCFF", "#FFCCFF", "#66CC00", "#FFCC33", "#669999", "#FF9999", "#6666FF", "#FF6633", "#66FFCC", "#006666",
"#FFFF00", "#66CCCC", "#FFCCCC", "#6699FF", "#FF99FF", "#669900", "#FF9933", "#66FFFF", "#996600", "#66FF00"
], colorSetIndex = [];
var icscroller = new InfiniteCircularScroller({
var icscroller = new InfiniteCircularScroll({
scroller: $(element),
wrapper: $(element).find("div"),
elementHeight: 21,
@@ -192,7 +35,8 @@
});
icscroller.setRenderFunc(function( $element, index, elementData ) {
var marginLeft = getMarginLeft(elementData);
$element.find("div.clickable-bar").css({
$element[ index == this._selectedRow ? "addClass" : "removeClass" ]("timeline-bar-selected")
.find("div.clickable-bar").css({
width : getWidth(elementData) + "px",
backgroundColor : getColorByString(elementData[scope.key.applicationName]),
marginLeft : marginLeft + "px"
@@ -224,8 +68,10 @@
scope.newCallStacks = filterCallStacks();
icscroller.setSource( scope.newCallStacks )
.setViewAreaHeight( $(element).parentsUntil("div.wrapper").height() - 70 ) // 70 is header area height
.setSelectedRow(-1, angular.noop)
.reset();
scope.maxHeight = icscroller.getContentsAreaHeight();
searchStartIndex = 0;
scope.$digest();
};
@@ -276,6 +122,39 @@
scope.$on('timelineDirective.resize', function (event) {
icscroller.resize($(element).parentsUntil("div.wrapper").height() - 70);
});
scope.$on("timelineDirective.searchCall", function( event, time, index ) {
var resultIndex = searchTime( searchStartIndex, -1, time );
if ( resultIndex == -1 ) {
if ( searchStartIndex == 0 ) {
scope.$emit("transactionDetail.timelineSearchCallResult", "No call took longer than {time}ms." );
} else {
resultIndex = searchTime( 0, searchStartIndex, time );
if ( resultIndex == -1 ) {
scope.$emit("transactionDetail.timelineSearchCallResult", "No call took longer than {time}ms." );
} else {
searchSuccess(resultIndex, "Loop");
}
}
} else {
searchSuccess(resultIndex, "");
}
});
searchTime = function( from, to, time ) {
return icscroller.searchRow(from, to, function( elementData ) {
if ( elementData[scope.key.end] - elementData[scope.key.begin] >= time ) {
return true;
}
return false;
});
};
searchSuccess = function(resultIndex, message) {
icscroller.setSelectedRow( resultIndex, function(newIndex) {
this.$wrapper.find("div[data-index=" + this._selectedRow + "]").removeClass("timeline-bar-selected");
this.$wrapper.find("div[data-index=" + newIndex + "]").addClass("timeline-bar-selected");
}).moveByRow( resultIndex );
searchStartIndex = resultIndex + 1;
scope.$emit("transactionDetail.timelineSearchCallResult", message );
}
}
};
});
+1
View File
@@ -95,6 +95,7 @@
<script src="components/amcharts/themes/light.js?v=${buildTime}"></script>
<script src="components/tooltipster/js/jquery.tooltipster.min.js?v=${buildTime}"></script>
<script src="components/handlebars/handlebars.min.js?v=${buildTime}"></script>
<script src="components/infiniteCircularScroll/InfiniteCircularScroll.js?v=${buildTime}"></script>
<script src="features/serverMap/jquery.ServerMap2.js?v=${buildTime}"></script>
<!-- endbuild -->
@@ -86,19 +86,26 @@
}
};
initSearchVar = function() {
$("#traceTabs li:nth-child(5)").hide();
// $("#traceTabs li:nth-child(5)").hide();
$scope.searchMinTime = 1000;
$scope.searchIndex = 0;
$scope.timelineSearchIndex = 0;
$scope.calltreeSearchIndex = 0;
$scope.searchMessage = "";
};
$scope.searchIndex = 0;
$scope.calltreeSearchIndex = 0;
$scope.timelineSearchIndex = 0;
$scope.searchMinTime = 1000; // ms
$scope.searchMessage = "";
$scope.searchCall = function() {
$scope.$broadcast('distributedCallFlowDirective.searchCall.forTransactionDetail', parseInt($scope.searchMinTime), parseInt($scope.searchIndex) );
if ( $("#CallStacks").is(":visible") ) {
$scope.$broadcast('distributedCallFlowDirective.searchCall.forTransactionDetail', parseInt($scope.searchMinTime), parseInt($scope.calltreeSearchIndex) );
} else {
$scope.$broadcast('timelineDirective.searchCall', parseInt($scope.searchMinTime), parseInt($scope.timelineSearchIndex) );
}
};
$scope.$watch( "searchMinTime", function( newVal ) {
$scope.searchIndex = 0;
$scope.calltreeSearchIndex = 0;
$scope.timelineSearchIndex = 0;
});
$scope.openInNewWindow = function () {
@@ -122,13 +129,23 @@
$("#traceTabs li:nth-child(1) a").trigger("click");
$scope.$broadcast('distributedCallFlowDirective.selectRow.forTransactionDetail', rowId);
});
$scope.$on("transactionDetail.searchCallresult", function(event, message) {
$scope.$on("transactionDetail.calltreeSearchCallResult", function(event, message) {
if ( message == "Loop" ) {
$scope.searchIndex = 1;
$scope.calltreeSearchIndex = 1;
} else {
$scope.searchMessage = message.replace("{time}", $scope.searchMinTime);
if ( message == "" ) {
$scope.searchIndex++;
$scope.calltreeSearchIndex++;
}
}
});
$scope.$on("transactionDetail.timelineSearchCallResult", function(event, message) {
if ( message == "Loop" ) {
$scope.timelineSearchIndex = 1;
} else {
$scope.searchMessage = message.replace("{time}", $scope.searchMinTime);
if ( message == "" ) {
$scope.timelineSearchIndex++;
}
}
});
@@ -137,7 +154,7 @@
$('#traceTabs li a[data-toggle="tab"]').on('shown.bs.tab', function(e) {
if ( e.target.href.indexOf( "#CallStacks") != -1 ) {
$at($at.CALLSTACK, $at.CLK_DISTRIBUTED_CALL_FLOW);
$("#traceTabs li:nth-child(5)").show();
// $("#traceTabs li:nth-child(5)").show();
}
});
// events binding
@@ -27,6 +27,9 @@
border-bottom:1px solid #D3D3D3;
font-family:Verdana;
}
.timeline-bar-selected {
background-color:orange;
}
.timeline-bar > div {
cursor:pointer;
margin-top:1px;