jui.define("chart.axis", [ "util.base" ], function(_) {
/**
* @class chart.axis
*
* Axis 를 관리하는 클래스
*
* * x 축
* * y 축
* * area { x, y, width, height}
* * data Axis 에 적용될 데이타
*
*/
var Axis = function(chart, originAxis, cloneAxis) {
var self = this,
map = null;
var _area = {},
_padding = {},
_clipId = "",
_clipPath = null,
_clipRectId = "",
_clipRect = null;
function calculatePanel(a, padding) {
a.x = getRate(a.x, chart.area('width'));
a.y = getRate(a.y, chart.area('height'));
a.width = getRate(a.width, chart.area('width'));
a.height = getRate(a.height, chart.area('height'));
a.x2 = a.x + a.width;
a.y2 = a.y + a.height;
// 패딩 개념 추가
a.x += padding.left || 0;
a.y += padding.top || 0;
a.x2 -= padding.right || 0;
a.y2 -= padding.bottom || 0;
a.width = a.x2 - a.x;
a.height = a.y2 - a.y;
return a;
}
function getRate(value, max) {
if(_.typeCheck("string", value) && value.indexOf("%") > -1) {
return max * (parseFloat(value.replace("%", "")) /100);
}
return value;
}
function drawGridType(axis, k) {
if((k == "x" || k == "y" || k == "z") && !_.typeCheck("object", axis[k]))
return null;
// 축 위치 설정
axis[k] = axis[k] || {};
if (k == "x") {
axis[k].orient = axis[k].orient == "top" ? "top" : "bottom";
} else if (k == "y") {
axis[k].orient = axis[k].orient == "right" ? "right" : "left";
} else if (k == "z") {
axis[k].orient = "center";
} else if (k == "c") {
axis[k].type = axis[k].type || "panel";
axis[k].orient = "custom";
}
axis[k].type = axis[k].type || "block";
var Grid = jui.include("chart.grid." + axis[k].type);
// 그리드 기본 옵션과 사용자 옵션을 합침
jui.defineOptions(Grid, axis[k]);
// 엑시스 기본 프로퍼티 정의
var obj = new Grid(chart, axis, axis[k]);
obj.chart = chart;
obj.axis = axis;
obj.grid = axis[k];
obj.svg = chart.svg;
var elem = obj.render();
// 그리드 별 위치 선정하기 (z축이 없을 때)
if(!self.isFull3D()) {
if (axis[k].orient == "left") {
elem.root.translate(chart.area("x") + self.area("x") - axis[k].dist, chart.area("y"));
} else if (axis[k].orient == "right") {
elem.root.translate(chart.area("x") + self.area("x2") + axis[k].dist, chart.area("y"));
} else if (axis[k].orient == "bottom") {
elem.root.translate(chart.area("x"), chart.area("y") + self.area("y2") + axis[k].dist);
} else if (axis[k].orient == "top") {
elem.root.translate(chart.area("x"), chart.area("y") + self.area("y") - axis[k].dist);
} else {
if (elem.root) elem.root.translate(chart.area("x") + self.area("x"), chart.area("y") + self.area("y"));
}
}
elem.scale.type = axis[k].type;
elem.scale.root = elem.root;
return elem.scale;
}
function drawMapType(axis, k) {
if(k == "map" && !_.typeCheck("object", axis[k])) return null;
// 축 위치 설정
axis[k] = axis[k] || {};
var Map = jui.include("chart.map");
// 맵 기본 옵션과 사용자 옵션을 합침
jui.defineOptions(Map, axis[k]);
// 맵 객체는 한번만 생성함
if(map == null) {
map = new Map(chart, axis, axis[k]);
}
// 맵 기본 프로퍼티 설정
map.chart = chart;
map.axis = axis;
map.map = axis[k];
map.svg = chart.svg;
// 그리드 별 위치 선정하기
var elem = map.render();
elem.root.translate(chart.area("x") + self.area("x"), chart.area("y") + self.area("y"));
elem.scale.type = axis[k].type;
elem.scale.root = elem.root;
return elem.scale;
}
function setScreen(pNo) {
var dataList = self.origin,
limit = self.buffer,
maxPage = Math.ceil(dataList.length / limit);
// 최소 & 최대 페이지 설정
if(pNo < 1) {
self.page = 1;
} else {
self.page = (pNo > maxPage) ? maxPage : pNo;
}
self.start = (self.page - 1) * limit, self.end = self.start + limit;
// 마지막 페이지 처리
if(self.end > dataList.length) {
self.start = dataList.length - limit;
self.end = dataList.length;
}
if(self.end <= dataList.length) {
self.start = (self.start < 0) ? 0 : self.start;
self.data = dataList.slice(self.start, self.end);
if(dataList.length > 0) self.page++;
}
}
function setZoom(start, end) {
var dataList = self.origin;
self.end = (end > dataList.length) ? dataList.length : end;
self.start = (start < 0) ? 0 : start;
self.data = dataList.slice(self.start, self.end);
}
function createClipPath() {
// clippath with x, y
if (_clipPath) {
_clipPath.remove();
_clipPath = null;
}
_clipId = _.createId("clip-id-");
_clipPath = chart.svg.clipPath({
id: _clipId
}, function() {
chart.svg.rect({
x: _area.x,
y: _area.y,
width: _area.width,
height: _area.height
});
});
chart.appendDefs(_clipPath);
// clippath without x, y
if (_clipRect) {
_clipRect.remove();
_clipRect = null;
}
_clipRectId = _.createId("clip-rect-id-");
_clipRect = chart.svg.clipPath({
id: _clipRectId
}, function() {
chart.svg.rect({
x: 0,
y: 0,
width: _area.width,
height: _area.height
});
});
chart.appendDefs(_clipRect);
}
function checkAxisPoint(e) {
var top = self.area("y"),
left = self.area("x");
if((e.chartY > top && e.chartY < top + self.area("height")) &&
(e.chartX > left && e.chartX < left + self.area("width"))) {
e.axisX = e.chartX - left;
e.axisY = e.chartY - top;
return true;
}
return false;
}
function setAxisMouseEvent() {
var isMouseOver = false,
index = cloneAxis.index;
chart.on("chart.mousemove", function(e) {
if(checkAxisPoint(e)) {
if(!isMouseOver) {
chart.emit("axis.mouseover", [ e, index ]);
isMouseOver = true;
}
} else {
if(isMouseOver) {
chart.emit("axis.mouseout", [ e, index ]);
isMouseOver = false;
}
}
if(checkAxisPoint(e)) {
chart.emit("axis.mousemove", [e, index]);
}
});
chart.on("bg.mousemove", function(e) {
if(!checkAxisPoint(e) && isMouseOver) {
chart.emit("axis.mouseout", [ e, index ]);
isMouseOver = false;
}
});
chart.on("chart.mousedown", function(e) {
if(!checkAxisPoint(e)) return;
chart.emit("axis.mousedown", [ e, index ]);
});
chart.on("chart.mouseup", function(e) {
if(!checkAxisPoint(e)) return;
chart.emit("axis.mouseup", [ e, index ]);
});
chart.on("chart.click", function(e) {
if(!checkAxisPoint(e)) return;
chart.emit("axis.click", [ e, index ]);
});
chart.on("chart.dbclick", function(e) {
if(!checkAxisPoint(e)) return;
chart.emit("axis.dbclick", [ e, index ]);
});
chart.on("chart.rclick", function(e) {
if(!checkAxisPoint(e)) return;
chart.emit("axis.rclick", [ e, index ]);
});
chart.on("chart.mousewheel", function(e) {
if(!checkAxisPoint(e)) return;
chart.emit("axis.mousewheel", [ e, index ]);
});
}
function drawAxisBackground() {
var bw = chart.theme("axisBorderWidth"),
lr = _padding.left + _padding.right,
tb = _padding.top + _padding.bottom;
var bg = chart.svg.rect({
rx: chart.theme("axisBorderRadius"),
ry: chart.theme("axisBorderRadius"),
fill: chart.theme("axisBackgroundColor"),
"fill-opacity": chart.theme("axisBackgroundOpacity"),
stroke: chart.theme("axisBorderColor"),
"stroke-width": bw,
width: _area.width + lr - bw,
height: _area.height + tb - bw,
x: _area.x - _padding.left,
y: _area.y - _padding.top
});
bg.translate(chart.area("x"), chart.area("y"));
return bg;
}
function init() {
_.extend(self, {
data : cloneAxis.data,
origin : cloneAxis.origin,
buffer : cloneAxis.buffer,
shift : cloneAxis.shift,
index : cloneAxis.index,
page : cloneAxis.page,
start : cloneAxis.start,
end : cloneAxis.end,
degree : cloneAxis.degree,
depth : cloneAxis.depth,
perspective : cloneAxis.perspective
});
// 원본 데이터 설정
self.origin = self.data;
// 페이지 초기화
if(self.start > 0 || self.end > 0) {
setZoom(self.start, self.end);
} else {
setScreen(self.page);
}
// 엑시스 이벤트 설정
setAxisMouseEvent();
// Grid 및 Area 설정
self.reload(cloneAxis);
}
/**
* @method getValue
*
* 특정 필드의 값을 맵핑해서 가지고 온다.
*
* @param {Object} data row data
* @param {String} fieldString 필드 이름
* @param {String/Number/Boolean/Object} [defaultValue=''] 기본값
* @return {Mixed}
*/
this.getValue = function(data, fieldString, defaultValue) {
var value = data[cloneAxis.keymap[fieldString]];
if (!_.typeCheck("undefined", value)) {
return value;
}
value = data[fieldString];
if (!_.typeCheck("undefined", value)) {
return value;
}
return defaultValue;
}
/**
* @method reload
*
* Axis 의 x,y,z 축을 다시 생성한다.
* * *
* @param {Object} options
*/
this.reload = function(options) {
var area = chart.area();
_.extend(this, {
x : options.x,
y : options.y,
z : options.z,
c : options.c,
map : options.map
});
// 패딩 옵션 설정
if(_.typeCheck("integer", options.padding)) {
_padding = { left: options.padding, right: options.padding, bottom: options.padding, top: options.padding };
} else {
_padding = options.padding;
}
_area = calculatePanel(_.extend(options.area, {
x: 0, y: 0 , width: area.width, height: area.height
}, true), _padding);
// 클립 패스 설정
createClipPath();
this.root = drawAxisBackground();
this.x = drawGridType(this, "x");
this.y = drawGridType(this, "y");
this.z = drawGridType(this, "z");
this.c = drawGridType(this, "c");
this.map = drawMapType(this, "map");
this.buffer = options.buffer;
this.shift = options.shift;
this.index = options.index;
this.page = options.page;
this.start = options.start;
this.end = options.end;
this.degree = options.degree;
this.depth = options.depth;
this.perspective = options.perspective;
}
/**
* @method area
*
* Axis 의 표시 영역을 리턴한다.
*
* @param {"x"/"y"/"width"/'height"/null} key area's key
* @return {Number/Object} key 가 있으면 해당 key 의 value 를 리턴한다. 없으면 전체 area 객체를 리턴한다.
*/
this.area = function(key) {
return _.typeCheck("undefined", _area[key]) ? _area : _area[key];
}
/**
* Gets the top, bottom, left and right margin values.
*
* @param {"top"/"left"/"bottom"/"right"} key
* @return {Number/Object}
*/
this.padding = function(key) {
return _.typeCheck("undefined", _padding[key]) ? _padding : _padding[key];
}
/**
* @method get
*
* Axis 의 옵션 정보를 리턴한다.
*
* @param key
*/
this.get = function(type) {
var obj = {
area: _area,
padding: _padding,
clipId: _clipId,
clipRectId : _clipRectId
};
return obj[type] || cloneAxis[type];
}
/**
* @method set
*
* axis의 주요 프로퍼티를 업데이트한다.
*
* @param {"x"/"y"/"c"/"map"/"degree"/"padding"} type
* @param {Object} grid
*/
this.set = function(type, value, isReset) {
if(_.typeCheck("object", value)) {
if (isReset === true) {
originAxis[type] = _.deepClone(value);
cloneAxis[type] = _.deepClone(value);
} else {
_.extend(originAxis[type], value);
_.extend(cloneAxis[type], value);
}
} else {
originAxis[type] = value;
cloneAxis[type] = value;
}
if(chart.isRender()) chart.render();
}
/**
* @deprecated
* @method updateGrid
*
* grid 정보를 업데이트 한다.
*
* @param {"x"/"y"/"c"/"map"} type
* @param {Object} grid
*/
this.updateGrid = this.set;
/**
* @method update
*
* data 를 업데이트 한다.
*
* @param {Array} data
*/
this.update = function(data) {
this.origin = data;
this.page = 1;
this.start = 0;
this.end = 0;
this.screen(1);
}
/**
* @method screen
*
* 화면상에 보여줄 데이타를 페이징한다.
*
* @param {Number} pNo 페이지 번호
*/
this.screen = function(pNo) {
setScreen(pNo);
if(this.end <= this.origin.length) {
if(chart.isRender()) chart.render();
}
}
/**
* @method next
*
*/
this.next = function() {
var dataList = this.origin,
limit = this.buffer,
step = this.shift;
this.start += step;
var isLimit = (this.start + limit > dataList.length);
this.end = (isLimit) ? dataList.length : this.start + limit;
this.start = (isLimit) ? dataList.length - limit : this.start;
this.start = (this.start < 0) ? 0 : this.start;
this.data = dataList.slice(this.start, this.end);
if(chart.isRender()) chart.render();
}
/**
* @method prev
*/
this.prev = function() {
var dataList = this.origin,
limit = this.buffer,
step = this.shift;
this.start -= step;
var isLimit = (this.start < 0);
this.end = (isLimit) ? limit : this.start + limit;
this.start = (isLimit) ? 0 : this.start;
this.data = dataList.slice(this.start, this.end);
if(chart.isRender()) chart.render();
}
/**
* @method zoom
*
* 특정 인덱스의 영역으로 데이타를 다시 맞춘다.
*
* @param {Number} start
* @param {Number} end
*/
this.zoom = function(start, end) {
if(start == end) return;
setZoom(start, end);
if(chart.isRender()) chart.render();
}
this.isFull3D = function() {
return !_.typeCheck([ "undefined", "null" ], this.z);
}
init();
}
Axis.setup = function() {
/** @property {chart.grid.core} [x=null] Sets a grid on the X axis (see the grid tab). */
/** @property {chart.grid.core} [y=null] Sets a grid on the Y axis (see the grid tab). */
/** @property {chart.grid.core} [c=null] Sets a custom grid (see the grid tab). */
/** @property {chart.map} [map=null] Sets a chart map. */
/** @property {Array} [data=[]] Sets the row set data which constitute a chart. */
/** @property {Integer} [buffer=10000] Limits the number of elements shown on a chart. */
/** @property {Integer} [shift=1] Data shift count for the 'prev' or 'next' method of the chart builder. */
/** @property {Array} [origin=[]] [For read only] Original data initially set. */
/** @property {Integer} [page=1] [For read only] Page number of the data currently drawn. */
/** @property {Integer} [start=0] [For read only] Start index of the data currently drawn. */
/** @property {Integer} [end=0] [For read only] End index of the data currently drawn. */
return {
/** @cfg {Integer} [extend=null] Configures the index of an applicable grid group when intending to use already configured axis options. */
extend: null,
/** @cfg {chart.grid.core} [x=null] Sets a grid on the X axis (see the grid tab). */
x: null,
/** @cfg {chart.grid.core} [y=null] Sets a grid on the Y axis (see the grid tab). */
y: null,
/** @cfg {chart.grid.core} [z=null] Sets a grid on the Z axis (see the grid tab). */
z: null,
/** @cfg {chart.grid.core} [c=null] Sets a grid on the C axis (see the grid tab). */
c: null,
/** @cfg {chart.map.core} [map=null] Sets a map on the Map axis */
map : null,
/** @cfg {Array} [data=[]] Sets the row set data which constitute a chart. */
data: [],
/** @cfg {Array} [origin=[]] [Fore read only] Original data initially set. */
origin: [],
/** @cfg {Object} [keymap={}] grid's data key map */
keymap: {},
/** @cfg {Object} [area={}] set area(x, y, width, height) of axis */
area: {},
/**
* @cfg {Object} padding axis padding
* @cfg {Number} [padding.top=0] axis's top padding
* @cfg {Number} [padding.bottom=0] axis's bottom padding
* @cfg {Number} [padding.left=0] axis's left padding
* @cfg {Number} [padding.right=0] axis's right padding
*/
padding : {
top: 0,
bottom: 0,
left: 0,
right: 0
},
/** @cfg {Number} [buffer=10000] Limits the number of elements shown on a chart. */
buffer: 10000,
/** @cfg {Number} [shift=1] Data shift count for the 'prev' or 'next' method of the chart builder. */
shift: 1,
/** @cfg {Number} [page=1] Page number of the data currently drawn. */
page: 1,
/** @cfg {Number} [start=0] */
start: 0,
/** @cfg {Number} [end=0] */
end: 0,
/**
* @cfg {Object} Set degree of 3d chart
* @cfg {Number} [degree.x=0] axis's x-degree
* @cfg {Number} [degree.y=0] axis's y-degree
* @cfg {Number} [degree.z=0] axis's z-degree
*/
degree: {
x: 0,
y: 0,
z: 0
},
/** @cfg {Number} [depth=0] Set depth of 3d chart */
depth: 0,
/** @cfg {Number} [perspective=0.9] Set perspective values in the 3d chart */
perspective: 0.9
}
}
return Axis;
});