jui.define("chart.brush.core", [ "util.base", "util.dom" ], function(_, $) {
/**
* @class chart.brush.core
*
* implements core method for brush
*
* @abstract
* @extends chart.draw
* @requires jquery
* @requires util.base
*/
var CoreBrush = function() {
function getMinMaxValue(data, target) {
var seriesList = {},
targetList = {};
for(var i = 0; i < target.length; i++) {
if (!seriesList[target[i]]) {
targetList[target[i]] = [];
}
}
// 시리즈 데이터 구성
for(var i = 0, len = data.length; i < len; i++) {
var row = data[i];
for(var k in targetList) {
targetList[k].push(row[k]);
}
}
for(var key in targetList) {
seriesList[key] = {
min : Math.min.apply(Math, targetList[key]),
max : Math.max.apply(Math, targetList[key])
}
}
return seriesList;
}
this.drawAfter = function(obj) {
if(this.brush.clip !== false) {
obj.attr({ "clip-path" : "url(#" + this.axis.get("clipId") + ")" });
}
obj.attr({ "class" : "brush-" + this.brush.type });
obj.translate(this.chart.area("x"), this.chart.area("y")); // 브러쉬일 경우, 기본 좌표 설정
}
this.drawTooltip = function(fill, stroke, opacity) {
var self = this,
tooltip = null;
function draw() {
return self.chart.svg.group({ "visibility" : "hidden" }, function() {
self.chart.text({
fill : self.chart.theme("tooltipPointFontColor"),
"font-size" : self.chart.theme("tooltipPointFontSize"),
"font-weight" : self.chart.theme("tooltipPointFontWeight"),
"text-anchor" : "middle",
opacity: opacity
});
self.chart.svg.circle({
r: self.chart.theme("tooltipPointRadius"),
fill: fill,
stroke: stroke,
opacity: opacity,
"stroke-width": self.chart.theme("tooltipPointBorderWidth")
});
});
}
function show(orient, x, y, value) {
var text = tooltip.get(0);
text.element.textContent = value;
if(orient == "left") {
text.attr({ x: -7, y: 4, "text-anchor": "end" });
} else if(orient == "right") {
text.attr({ x: 7, y: 4, "text-anchor": "start" });
} else if(orient == "bottom") {
text.attr({ y: 16 });
} else {
text.attr({ y: -7 });
}
tooltip.attr({ visibility: (value != 0) ? "visible" : "hidden" });
tooltip.translate(x, y);
}
// 툴팁 생성
tooltip = draw();
return {
tooltip: tooltip,
control: show,
style: function(fill, stroke, opacity) {
tooltip.get(0).attr({
opacity: opacity
});
tooltip.get(1).attr({
fill: fill,
stroke: stroke,
opacity: opacity
})
}
}
}
/**
*
* @method curvePoints
*
* 좌표 배열 'K'에 대한 커브 좌표 'P1', 'P2'를 구하는 함수
*
* TODO: min, max 에 대한 처리도 같이 필요함.
*
* @param {Array} K
* @return {Object}
* @return {Array} return.p1
* @return {Array} return.p2
*
*/
this.curvePoints = function(K) {
var p1 = [];
var p2 = [];
var n = K.length - 1;
/*rhs vector*/
var a = [];
var b = [];
var c = [];
var r = [];
/*left most segment*/
a[0] = 0;
b[0] = 2;
c[0] = 1;
r[0] = K[0] + 2 * K[1];
/*internal segments*/
for ( i = 1; i < n - 1; i++) {
a[i] = 1;
b[i] = 4;
c[i] = 1;
r[i] = 4 * K[i] + 2 * K[i + 1];
}
/*right segment*/
a[n - 1] = 2;
b[n - 1] = 7;
c[n - 1] = 0;
r[n - 1] = 8 * K[n - 1] + K[n];
/*solves Ax=b with the Thomas algorithm (from Wikipedia)*/
for (var i = 1; i < n; i++) {
var m = a[i] / b[i - 1];
b[i] = b[i] - m * c[i - 1];
r[i] = r[i] - m * r[i - 1];
}
p1[n - 1] = r[n - 1] / b[n - 1];
for (var i = n - 2; i >= 0; --i)
p1[i] = (r[i] - c[i] * p1[i + 1]) / b[i];
/*we have p1, now compute p2*/
for (var i = 0; i < n - 1; i++)
p2[i] = 2 * K[i + 1] - p1[i + 1];
p2[n - 1] = 0.5 * (K[n] + p1[n - 1]);
return {
p1 : p1,
p2 : p2
};
}
/**
*
* @method eachData
*
* loop axis data
*
* @param {Function} callback
*/
this.eachData = function(callback, reverse) {
if(!_.typeCheck("function", callback)) return;
var list = this.listData();
if(reverse === true) {
for(var len = list.length - 1; len >= 0; len--) {
callback.call(this, len, list[len]);
}
} else {
for(var index = 0, len = list.length; index < len; index++) {
callback.call(this, list[index], index);
}
}
}
/**
*
* @method listData
*
* get axis.data
*
* @returns {Array} axis.data
*/
this.listData = function() {
if(!this.axis) {
return [];
} else {
if(!this.axis.data) {
return [];
}
}
return this.axis.data;
}
/**
*
* @method getData
*
* get record by index in axis.data
*
* @param {Integer} index
* @returns {Object} record in axis.data
*/
this.getData = function(index) {
return this.listData()[index];
}
/**
* @method getValue
*
* chart.axis.getValue alias
*
* @param {Object} data row data
* @param {String} fieldString 필드 이름
* @param {String/Number/Boolean/Object} [defaultValue=''] 기본값
* @return {Mixed}
*/
this.getValue = function(data, fieldString, defaultValue) {
return this.axis.getValue(data, fieldString, defaultValue);
}
/**
*
* @method getXY
*
* 차트 데이터에 대한 좌표 'x', 'y'를 구하는 함수
*
* @param {Boolean} [isCheckMinMax=true]
* @return {Array}
*/
this.getXY = function(isCheckMinMax) {
var xy = [],
series = {},
length = this.listData().length,
i = length,
target = this.brush.target,
targetLength = target.length;
if(isCheckMinMax !== false) {
series = getMinMaxValue(this.axis.data, target);
}
for(var j = 0; j < targetLength; j++) {
xy[j] = {
x: new Array(length),
y: new Array(length),
value: new Array(length),
min: [],
max: [],
length: length
};
}
var axisData = this.axis.data,
isRangeY = (this.axis.y.type == "range"),
x = this.axis.x,
y = this.axis.y,
func = _.loop(i);
func(function(i, group) {
var data = axisData[i],
startX = 0,
startY = 0;
if(isRangeY) startX = x(i);
else startY = y(i);
for(var j = 0; j < targetLength ; j++) {
var key = target[j],
value = data[key];
if(isRangeY) startY = y(value);
else startX = x(value);
xy[j].x[i] = startX;
xy[j].y[i] = startY;
xy[j].value[i] = value;
if(isCheckMinMax !== false) {
xy[j].min[i] = (value == series[key].min);
xy[j].max[i] = (value == series[key].max);
}
}
})
return xy;
}
/**
*
* @method getStackXY
*
* 차트 데이터에 대한 좌표 'x', 'y'를 구하는 함수
* 단, 'y' 좌표는 다음 데이터 보다 높게 구해진다.
*
* @param {Boolean} [isCheckMinMax=true]
* @return {Array}
*/
this.getStackXY = function(isCheckMinMax) {
var xy = this.getXY(isCheckMinMax),
isRangeY = (this.axis.y.type == "range");
this.eachData(function(data, i) {
var valueSum = 0;
for(var j = 0; j < this.brush.target.length; j++) {
var key = this.brush.target[j],
value = data[key];
if(j > 0) {
valueSum += data[this.brush.target[j - 1]];
}
if(isRangeY) {
xy[j].y[i] = this.axis.y(value + valueSum);
} else {
xy[j].x[i] = this.axis.x(value + valueSum);
}
}
});
return xy;
}
/**
* @method addEvent
* 브러쉬 엘리먼트에 대한 공통 이벤트 정의
*
* @param {Element} element
* @param {Integer} dataIndex
* @param {Integer} targetIndex
*/
this.addEvent = function(elem, dataIndex, targetIndex) {
if(this.brush.useEvent !== true) return;
var chart = this.chart,
obj = {};
if(_.typeCheck("object", dataIndex) && !targetIndex) {
obj.brush = this.brush;
obj.data = dataIndex;
} else {
obj.brush = this.brush;
obj.dataIndex = dataIndex;
obj.dataKey = (targetIndex != null) ? this.brush.target[targetIndex] : null;
obj.data = (dataIndex != null) ? this.getData(dataIndex) : null;
}
elem.on("click", function(e) {
setMouseEvent(e);
chart.emit("click", [ obj, e ]);
});
elem.on("dblclick", function(e) {
setMouseEvent(e);
chart.emit("dblclick", [ obj, e ]);
});
elem.on("contextmenu", function(e) {
setMouseEvent(e);
chart.emit("rclick", [ obj, e ]);
e.preventDefault();
});
elem.on("mouseover", function(e) {
setMouseEvent(e);
chart.emit("mouseover", [ obj, e ]);
});
elem.on("mouseout", function(e) {
setMouseEvent(e);
chart.emit("mouseout", [ obj, e ]);
});
elem.on("mousemove", function(e) {
setMouseEvent(e);
chart.emit("mousemove", [ obj, e ]);
});
elem.on("mousedown", function(e) {
setMouseEvent(e);
chart.emit("mousedown", [ obj, e ]);
});
elem.on("mouseup", function(e) {
setMouseEvent(e);
chart.emit("mouseup", [ obj, e ]);
});
function setMouseEvent(e) {
var pos = $.offset(chart.root),
offsetX = e.pageX - pos.left,
offsetY = e.pageY - pos.top;
e.bgX = offsetX;
e.bgY = offsetY;
e.chartX = offsetX - chart.padding("left");
e.chartY = offsetY - chart.padding("top");
}
}
/**
* @method color
*
* chart.color() 를 쉽게 사용할 수 있게 만든 유틸리티 함수
*
* @param {Number} key1 브러쉬에서 사용될 컬러 Index
* @param {Number} key2 브러쉬에서 사용될 컬러 Index
* @returns {*}
*/
this.color = function(key1, key2) {
var colors = this.brush.colors,
color = null,
colorIndex = 0,
rowIndex = 0;
if(!_.typeCheck("undefined", key2)) {
colorIndex = key2;
rowIndex = key1;
} else {
colorIndex = key1;
}
if(_.typeCheck("function", colors)) {
var newColor = colors.call(this.chart, this.getData(rowIndex), rowIndex);
if(_.typeCheck([ "string", "integer" ], newColor)) {
color = this.chart.color(newColor);
} else if(_.typeCheck("array", newColor)) {
color = this.chart.color(colorIndex, newColor);
} else {
color = this.chart.color(0);
}
} else {
color = this.chart.color(colorIndex, colors);
}
return color;
}
/**
* @method offset
*
* 그리드 타입에 따른 시작 좌표 가져오기 (블럭)
*
* @param {String} 그리드 종류
* @param {Number} 인덱스
* @returns {*}
*/
this.offset = function(type, index) { // 그리드 타입에 따른 시작 좌표 가져오기
var res = this.axis[type](index);
if(this.axis[type].type != "block") {
res += this.axis[type].rangeBand() / 2;
}
return res;
}
}
CoreBrush.setup = function() {
return {
/** @property {chart.builder} chart */
/** @property {chart.axis} axis */
/** @property {Object} brush */
/** @cfg {Array} [target=null] Specifies the key value of data displayed on a brush. */
target: null,
/** @cfg {Array/Function} [colors=null] Able to specify color codes according to the target order (basically, refers to the color codes of a theme) */
colors: null,
/** @cfg {Integer} [axis=0] Specifies the index of a grid group which acts as the reference axis of a brush. */
axis: 0,
/** @cfg {Integer} [index=null] [Read Only] Sequence index on which brush is drawn. */
index: null,
/** @cfg {boolean} [clip=true] If the brush is drawn outside of the chart, cut the area. */
clip: true,
/** @cfg {boolean} [useEvent=true] If you do not use a brush events, it gives better performance. */
useEvent: true
}
}
/**
* @event click
* Event that occurs when clicking on the brush.
* @param {BrushData} obj Related brush data.
*/
/**
* @event dblclick
* Event that occurs when double clicking on the brush.
* @param {BrushData} obj Related brush data.
*/
/**
* @event rclick
* Event that occurs when right clicking on the brush.
* @param {BrushData} obj Related brush data.
*/
/**
* @event mouseover
* Event that occurs when placing the mouse over the brush.
* @param {BrushData} obj Related brush data.
*/
/**
* @event mouseout
* Event that occurs when moving the mouse out of the brush.
* @param {BrushData} obj Related brush data.
*/
/**
* @event mousemove
* Event that occurs when moving the mouse over the brush.
* @param {BrushData} obj Related brush data.
*/
/**
* @event mousedown
* Event that occurs when left clicking on the brush.
* @param {BrushData} obj Related brush data.
*/
/**
* @event mouseup
* Event that occurs after left clicking on the brush.
* @param {BrushData} obj Related brush data.
*/
return CoreBrush;
}, "chart.draw");