/*
* IMC 의 장비 가동상태바 를 도시하기 위한 플러그인
* by MHD
*/
$.widget( "imc.barchart", {
// default options
options: {
margin : 20,
statusColor: {
auto : "#219244"
, idling : "#eb9934"
, setup : "#595858"
, change : "#00a0e9"
, alarm : "#ca1134"
, off : "#9e9e9f"
, defaultColor : "#FFFFFF"
},
oee : {
second : "second"
, startDateTime : "start_date"
, status : "status"
},
tickColor : "#AAAAAA",
tickWidth : 1,
dutyTime : "00:00",
hourTxt : '시간' ,
minTxt : '분' ,
toolTipPosition : 'up', // tooltip의 위치 up, down
isShowTooltip : true ,
isLimitMoveTooltip : false,
// callbacks
onChangedDate: null , //function(e, addDays){} param +1 다음날, -1 전날 날짜 변환 이벤트 발생 function(e, addDays)
// 선언되지 않으면 날짜 변경이 되지 않음 .. 즉 당일꺼만 볼 경우... 날짜이동이 없는 경우
onClickBarChart : null ,// functiono(e, timeTxt) 클릭시 콜백 timeTxt 값을 리턴
onChangedViewType : null ,//function(e, t){} t = 'day', 'hour', 'min'
onChangedTooltip : null, // functiono(e, left, timeTxt, isShow) left 위치좌표, top 은 필요없음, isShow 로 마우스 over 상태 확인, txt 로 시간 체크
onDrawBar : null // function(e, oeeResult) oeeResult 안에는 각 상태별 시간과 count 값이 json 구조로 저장되어 있음
},
_oeeType : {
day:{
rate:24*60*60,
txt: function(){
this.options.hourTxt;
return 24 + this.options.hourTxt;
},
getPointTimeTxt:function(lastPoint, width){ // 속도 때문에 width 를 매번 계산하지 않는다 차이가 좀남
var rate =this._cunrrentType.rate/60/width;
return this.DateUtil.DateType.Min.execute(this._convertPx(lastPoint*rate + this.oeeTimeTxt[0].data("timepixel")));
}
} ,
hour:{
rate:60*60,
txt: function(){
this.options.hourTxt;
return 1 + this.options.hourTxt;
},
getPointTimeTxt:function(lastPoint, width){
return this._oeeType.day.getPointTimeTxt.call(this, lastPoint, width);
}
} ,
min:{
rate:12*60,
txt: function(){
this.options.hourTxt;
return 12 + this.options.minTxt;
},
getPointTimeTxt:function(lastPoint, width){
var rate =this._cunrrentType.rate/width;
var x = lastPoint*rate + this.oeeTimeTxt[0].data("timepixel")*60;
return this.DateUtil.DateType.Second.execute(x >= 60*60*24 ? x%(60*60*24) : x, true);
}
}
},
_cunrrentType : null,
_lastData : [],
// the constructor
_create: function() {
this._setCurrentType("day");
this.oeeBar = $("<div>",{"class":"imc-oee-bar"});
this.oeeBar.css({height : this.element.height()-30, width:this.element.width()-this.options.margin*2, "marginLeft":this.options.margin});
this.oeeTimeTick = $('<svg xmlns="http://www.w3.org/2000/svg" height=50 width='+this.element.width()+'>');
this.tooltip = $("<div>",{"class":"imc-oee-tooltip " + this.options.toolTipPosition}).append(this._tooltipHtml())
this.oeeTimeTxt = [];
this.element
.addClass( "imc-oee" )
.disableSelection().append(this.oeeBar).append(this.oeeTimeTick).append(this.tooltip);
this._drawTimeTick();
this.tooltipManager.initialize.call(this);
},
_refresh: function() {
this._drawbar(this._lastData, this.oeeTimeTxt[0].data("timepixel"));
this.tooltip.empty().append(this._tooltipHtml());
this.tooltipManager.initialize.call(this);
},
_destroy: function() {
this.oeeBar.remove();
this.oeeTimeTick.remove();
this.tooltip.remove();
this.element
.removeClass( "imc-oee-bar" )
.enableSelection()
.css( "background-color", "transparent" ).empty();
},
_setOptions: function() {
this._superApply( arguments );
this._refresh();
},
_setOption: function( key, value ) {
if("dutyTime" == key && !/[0-9]{2}.[0-9]{2}/.test(value)){
return;
}
if(/oee|statusColor/.test(key)){
value = $.extend({}, this.options[key], value);
}
this._super( key, value );
},
_drawTimeTick : function(){
var w = this.element.width();
var h = this.element.height();
var style = "stroke:"+this.options.tickColor+";stroke-width:"+this.options.tickWidth
this.oeeTimeTick.append(this._makeSVG('line', {
x1 : this.options.margin ,
x2 : w-this.options.margin,
y1 : 0 ,
y2 : 0 ,
style : style
}));
for(var i = 0 ; i <= 24 ; i++){
var x = this.options.margin + (w-this.options.margin*2)/24*i;
this.oeeTimeTick.append(this._makeSVG('line', {
x1 : x ,
x2 : x ,
y1 : 0 ,
y2 : 10 ,
style : style
}));
if( i % 6 == 0 ) {
this._drawTimeTxt(x, i / 6, this._createTime(x));
}
}
this._updateTimeTxt(0, this.oeeTimeTxt[0].data("orgpixel"));
},
_drawTimeTxt : function(x, index, $time){
var times = this.options.dutyTime.split(":");
var hour = parseInt(times[0]) + index * 6;
hour = hour > 24 ? hour % 24 : hour;
$time.data("orgpixel", parseInt(times[0])* 60 + parseInt(times[1]));
$time.data("timepixel", parseInt(times[0])* 60 + parseInt(times[1]));
this.element.append($time);
},
_createTime : function(x){
this.oeeTimeTxt[this.oeeTimeTxt.length] = $("<time>",{"class":"imc-oee-timetxt",text:"00:00",
style :["left:", (x-18), "px;top:", ( this.element.height()-10), "px;"].join("")});
return this.oeeTimeTxt[this.oeeTimeTxt.length-1];
},
_updateTimeTxt : function(x, startTime){
var rate = this._cunrrentType.rate/60, DateUtil = this.DateUtil, _convertPx = this._convertPx;
startTime += startTime < this._getSecond(this.options.dutyTime)/60 ? 1440 : 0;
x = parseInt(x/rate) * rate + (startTime ? startTime : 0), rate /= 4;
$.each(this.oeeTimeTxt, function( index, $this ) {
var px = x + index * rate;
$this.text(DateUtil.getString(DateUtil.DateType.Min, _convertPx(px), false));
$this.data("timepixel", px)
});
},
_findStartPoint : function(html, data, startMin, w, status, oeeResult){
var startSec = startMin*60, seconds = 0, dutySec = this._getSecond(this.options.dutyTime), pixel = 0;
startSec += (startSec < dutySec ? 60*60*24 : 0)
for (var i = 0, len = data.length; i < len; i++) {
seconds = this._getSecond(data[i][this.options.oee.startDateTime])
seconds = startSec > seconds && dutySec > seconds ? (seconds += 60*60*24) : seconds;
if(seconds > startSec){
pixel = (seconds- startSec)/this._cunrrentType.rate * w;
if(i > 0){
status = data[i-1][status];
this._addOeeResult(oeeResult[status], seconds- startSec);
}else{
status = "defaultColor";
}
return this._checkOverFirstPixel(html, status, pixel, w, i)
}else if(i == len -1 && seconds < startSec && startSec < seconds + parseInt(data[i][this.options.oee.second])){
pixel = (seconds + parseInt(data[i][this.options.oee.second])-startSec)/this._cunrrentType.rate * w;
return this._checkOverFirstPixel(html, data[i][status], pixel, w, -1)
}
}
return {index : -1, pixel : pixel};
},
_checkOverFirstPixel : function(html, status, pixel, w, i){
if(pixel > w){ // pixel 이 넓이 보다 큰 경우
i = -1, pixel = w;
}
this._addbaritem(html, status, pixel, w);
return {index : i, pixel : pixel}
},
_drawbar : function(data, startMin) {
var html = [], w = this.oeeBar.width(), oeeResult = this._getOeeTemplete(), totalPixel = 0, pixel, leakPixel=0,
status = this.options.oee.status, second = this.options.oee.second, startDateTime = this.options.oee.startDateTime;
for (var i = 0, len = data.length; i < len; i++) {
if(i == 0 && (this.oeeTimeTxt[0].data("orgpixel") != startMin || this.oeeTimeTxt[0].data("orgpixel") != this._getSecond(data[i][startDateTime])/60)){
var first = this._findStartPoint(html, data, startMin, w, status, oeeResult);
totalPixel =+ first.pixel;
i = first.index;
if(first.index < 0) break;
}
pixel = (data[i][second]/this._cunrrentType.rate) * w;
if(totalPixel + pixel > w){
pixel = w - totalPixel;
this._addOeeResult(oeeResult[data[i][status]], parseInt(pixel*this._cunrrentType.rate/w));
this._addbaritem(html, data[i][status], pixel, w);
break;
}
leakPixel += pixel;
pixel = leakPixel - leakPixel%1;
leakPixel = leakPixel - pixel;
totalPixel += pixel;
if(len - 1 == i && leakPixel > 0){ // 잃어버린 1px 을 찾아서
pixel += 1;
}
this._addOeeResult(oeeResult[data[i][status]], data[i][second]);
this._addbaritem(html, data[i][status], pixel, w);
}
this._bindEvent(this.oeeBar.empty().append(html.join('')).children(":not(.defaultColor)"));
if(this._cunrrentType == this._oeeType.day){ // day 는 굳이 다시 그릴필요 없음
this._trigger("onDrawBar", null , [oeeResult]);
}
},
_bindEvent: function(bar){
bar.on({mousemove: $.proxy(this.tooltipManager.calculatePoint, this),
mouseleave: $.proxy(this.tooltipManager.hideToolTip, this),
click : $.proxy(this.tooltipManager.clickPoint, this),
dblclick : $.proxy(this.tooltipManager.nextProgress, this)
}); // 이벤트는 우선 그냥 다 tooltip 에서 처리하자
},
_addbaritem : function(htmlarray, status, pixel, width) {
if(pixel > 0){
htmlarray.push('<span class="', status, '" style="width:', pixel, 'px;background-color:',this.options.statusColor[status] ,'"></span>');
}
},
_makeSVG :function(tag, attrs) {
var el= document.createElementNS('http://www.w3.org/2000/svg', tag);
for (var k in attrs)
el.setAttribute(k, attrs[k]);
return el;
},
_tooltipHtml : function(){
return ['<time>00:00</time>','<button type="button" data-value="day" class="on"><span>',this._oeeType.day.txt.call(this),'</span></button>'
,'<button type="button" data-value="hour"><span>',this._oeeType.hour.txt.call(this),'</span></button>'
,'<button type="button" data-value="min"><span>',this._oeeType.min.txt.call(this),'</span></button>', '<div class="arrow">'].join("");
},
_addOeeResult : function(oeeResult, sec){
if(sec > 0){
oeeResult.sec += parseInt(sec);
oeeResult.cnt ++;
}
},
_getOeeTemplete : function(){
var result = {}
for(var key in this.options.statusColor){
result[key] = { sec : 0 , cnt : 0 };
}
return result;
},
_setCurrentType : function(t){
if(this._oeeType[t]){
this._cunrrentType = this._oeeType[t];
}
},
_pad : function(value, length) {
value = String(value);
length = parseInt(length,10) || 2;
while (value.length < length) { value = '0' + value; }
return value;
},
// 24시간 기준 pixel 변환
_convertPx : function(val){
val = (val >= 1440) ? val%1440 : val;
val = (val < 0) ? 1440 + val%1440 : val;
return val;
},
DateUtil : (function(){
var sep = ':';
var DateType = {
Min : { execute :
function(min, useSec){
var hh = parseInt(min / 60);
min %= 60;
return [pad(hh,2),sep ,pad(parseInt(min),2) ,(useSec ? (sep+"00") : "")].join("");
}
},
Second : { execute :
function(sec, useSec){
var hh = parseInt(sec / 3600);
sec %= 3600;
var mm = parseInt(sec / 60), ss = parseInt(sec % 60);
return [pad(hh,2), pad(mm,2), useSec ? pad(ss,2) : ""].join(sep);
}
},
Hour :{ execute :
function(hour, useSec){
return [pad(parseInt(hour),2), "00", useSec ? "00" : ""].join(sep);
}
},
}
,getString = function(fn, val, useSec){
return fn.execute.call(this, val, useSec=== undefined ? true : useSec);
}
,pad = function(value, length) {
value = String(value);
length = parseInt(length,10) || 2;
while (value.length < length) { value = '0' + value; }
return value;
}
return{
DateType : DateType
,getString : getString
}
})(),
_getdate : function(datestring) {
var extracted = datestring.match(/([0-9]{4}).([0-9]{2}).([0-9]{2}) ([0-9]{2}).([0-9]{2}).([0-9]{2})/);
return extracted && extracted.length > 6 ? new Date(extracted[1], parseInt(extracted[2])-1, extracted[3], extracted[4], extracted[5], extracted[6]) : new Date();
},
_getSecond : function(datestring) { //XX:XX , XX:XX:XX 둘다 처리 굳이 정규식 필요없음, 횟수가 많을 경우 정규식이 더느림
var extracted = datestring.split(/[-: ]/);
var len = extracted.length;
return extracted && extracted.length > 1 ? (extracted.length % 3 == 0 ?
parseInt(extracted[len-3])*60*60 + parseInt(extracted[len-2])*60 + parseInt(extracted[len-1])
: parseInt(extracted[len-2])*60*60 + parseInt(extracted[len-1])*60 ) : 0;
},
_getTxt : function(lastPoint, width){
return this._cunrrentType.getPointTimeTxt.call(this, lastPoint, width);
},
_update : function(startMin){
this._updateTimeTxt(0, startMin);
this._drawbar(this._lastData, startMin);
},
// type : "min", "hour", "day"
setData : function(data, t, startDateTime){
this._lastData = data;
this.setStartDateTime(t, startDateTime);
},
setStartDateTime : function(t, startDateTime){
var startMin = t == "day" ? this.oeeTimeTxt[0].data("orgpixel") :
(startDateTime !== undefined ? this._getSecond(startDateTime)/60 : this.oeeTimeTxt[0].data("timepixel"));
this._setCurrentType(t);
this._updateTimeTxt(0, startMin);
this._drawbar(this._lastData, startMin);
},
clear : function(){
this.setData([], "day", this.options.dutyTime);
},
next : function(){
if( this._getSecond(this.options.dutyTime)/60 +1440 <= this.oeeTimeTxt[0].data("timepixel") + this._cunrrentType.rate/60){
if($.isFunction( this.options.onChangedDate)){
this._trigger("onChangedDate", null , [+1]);
this._updateTimeTxt(0, this.oeeTimeTxt[0].data("orgpixel"));
console.log("trigger onChangedDate +1 day")
}else if(this._cunrrentType != this._oeeType.day){ // day 는 굳이 다시 그릴필요 없음
this._update(this.oeeTimeTxt[0].data("orgpixel"));
}
}else{
this._update(this.oeeTimeTxt[0].data("timepixel") + this._cunrrentType.rate/60);
}
},
prev : function(){
if(this._getSecond(this.options.dutyTime)/60 > this.oeeTimeTxt[0].data("timepixel") - this._cunrrentType.rate/60){
if($.isFunction( this.options.onChangedDate)){
this._trigger("onChangedDate", null , [-1]);
this._updateTimeTxt(0, this.oeeTimeTxt[0].data("timepixel") - this._cunrrentType.rate/60);
console.log("trigger onChangedDate -1 day");
}else if(this._cunrrentType != this._oeeType.day){ // day 는 굳이 다시 그릴필요 없음
this._update(this.oeeTimeTxt[0].data("orgpixel") + 1440 - this._cunrrentType.rate/60);
}
}else{
this._update(this.oeeTimeTxt[0].data("timepixel") - this._cunrrentType.rate/60);
}
},
tooltipManager : (function(){
var displayed = false , hideToolTiptimer = [], lastPoint, width, pLeft, tWidth, tHWidth, width, aLeft,
$parent, $tooltip, $time, $tabs, $arrow;
// $.proxy 이부분에서는 빼고 내부변수로 바꾸자 굳이 proxy 사용할 이유가 없음(오히려 비효율적)
var initialize = function(){
$parent = this, $tooltip =this.tooltip, $time = $tooltip.find('time'), $tabs = $tooltip.find('button'),$arrow = $tooltip.find('.arrow');
aLeft = $arrow.position().left, pLeft = $tooltip.parent().offset().left, tWidth = $tooltip.width(), tHWidth = tWidth/2, width =this.oeeBar.width();
$tooltip.on({mouseenter: cancelHideToolTip, mouseleave: $.proxy(hideToolTip, this)});
$tabs.click($.proxy(function(e){
var $this = $(e.currentTarget), index = $this.index()-1, t = $this.data("value"),x = 0, rate = this._cunrrentType.rate;
this._setCurrentType(t);
if(t == 'day'){
x = this.oeeTimeTxt[0].data("orgpixel");
}else if(t == 'hour' && this._cunrrentType.rate > rate){
this._setCurrentType(t);
x = this.oeeTimeTxt[0].data("timepixel");
x = x - x % 60;
}else{
x = lastPoint * rate/60/width;
rate = this._cunrrentType.rate/60;
x = parseInt(x/rate) * rate +this.oeeTimeTxt[0].data("timepixel");
}
this._trigger("onChangedViewType", null , [t]);
$tabs.removeClass('on').eq(index).addClass('on');
this._update(x);
}, this));
}
, calculatePoint = function(e) {
if(this.options.isLimitMoveTooltip){
_calculateLPoint(e, this.options.margin + this.options.margin); //덧셈 속도때문에
}else{
$tooltip.css({left: e.clientX - pLeft - tHWidth});
}
lastPoint = e.clientX - pLeft- this.options.margin;
$time[0].innerHTML = this._getTxt(lastPoint, width);
if (this.options.isShowTooltip && !displayed) {
this.tooltip.show();
displayed = true;
}
cancelHideToolTip();
this._trigger("onChangedTooltip", null , [lastPoint, $time[0].innerHTML, true]);
}
, _calculateLPoint = function(e, fMargin) { // 속도가 왜 Math 함수가 더느릴까...
var x = e.clientX - pLeft - tHWidth;
var limit = width- tWidth + fMargin;
if(x > 0){
if(x < limit){
$tooltip.css({left:x});
$arrow.css({left:"50%"});
}else{
$tooltip.css({left:limit});
$arrow.css({left: x-limit+tHWidth});
}
}else{
$tooltip.css({left:0});
$arrow.css({left: x + tHWidth});
}
}
, hideToolTip = function(event) {
event.preventDefault()
hideToolTiptimer[hideToolTiptimer.length] = setTimeout( $.proxy(hideToolTipAction, this), 100);
}
, cancelHideToolTip = function(event) {
for(var i = 0, len = hideToolTiptimer.length; i < len ; i++){
clearTimeout(hideToolTiptimer[i]);
}
hideToolTiptimer = [];
}
, hideToolTipAction = function() {
$tooltip.hide();
this._trigger("onChangedTooltip", null , [-1, '', false]);
displayed = false;
}
, nextProgress = function(e){
lastPoint = e.clientX - pLeft- this.options.margin;
var $next = $tabs.filter(".on").next("button");
if($next.length > 0 ){
$next.trigger("click");
}else{
$tabs.first().trigger("click");
}
}
, clickPoint = function(){
this._trigger("onClickBarChart", null , [this._getTxt(lastPoint, width)]);
}
, clear = function(){
$tabs.removeClass("on").eq(0).addClass("on");
};
return {
calculatePoint : calculatePoint
, hideToolTip : hideToolTip
, cancelHideToolTip : cancelHideToolTip
, nextProgress : nextProgress
, clear : clear
, clickPoint : clickPoint
, initialize : initialize
}
})()
});