jui.define("chart.map", [ "util.base", "util.dom", "util.math", "util.svg" ], function(_, $, math, SVG) {
/**
* @class chart.grid.core
* @extends chart.draw
* @abstract
*/
var Map = function() {
var self = this;
var pathData = {},
pathGroup = null,
pathIndex = {},
pathScale = 1,
pathX = 0,
pathY = 0;
function loadArray(data) {
var children = [];
for(var i = 0, len = data.length; i < len; i++) {
if(_.typeCheck("object", data[i])) {
var style = {};
if(_.typeCheck("string", data[i].style)) {
style = getStyleObj(data[i].style);
delete data[i].style;
}
var elem = SVG.createObject({
type: (data[i].d != null) ? "path" : "polygon",
attr: data[i]
});
// Set styles
elem.attr(_.extend(style, {
fill: self.chart.theme("mapPathBackgroundColor"),
"fill-opacity": self.chart.theme("mapPathBackgroundOpacity"),
stroke: self.chart.theme("mapPathBorderColor"),
"stroke-width": self.chart.theme("mapPathBorderWidth"),
"stroke-opacity": self.chart.theme("mapPathBorderOpacity")
}));
children.push({
path: elem,
data: data[i]
});
}
}
function getStyleObj(str) {
var style = {},
list = str.split(";");
for(var i = 0; i < list.length; i++) {
if(list[i].indexOf(":") != -1) {
var obj = list[i].split(":");
style[_.trim(obj[0])] = _.trim(obj[1]);
}
}
return style;
}
return children;
}
function getPathList(root) {
if(!_.typeCheck("string", root.id)) return;
var pathData = [],
children = root.childNodes;
for(var i = 0, len = children.length; i < len; i++) {
var elem = children[i],
name = elem.nodeName.toLowerCase();
if(elem.nodeType != 1) continue;
if(name == "g") {
pathData = pathData.concat(getPathList(elem));
} else if(name == "path" || name == "polygon") {
var obj = { group: root.id };
for(var key in elem.attributes) {
var attr = elem.attributes[key];
if(attr.specified && isLoadAttribute(attr.name)) {
obj[attr.name] = replaceXYValue(attr);
}
}
if(_.typeCheck("string", obj.id)) {
_.extend(obj, getDataById(obj.id));
}
pathData.push(obj);
}
}
return pathData;
}
function loadPath(uri) {
// 해당 URI의 데이터가 존재할 경우
if(_.typeCheck("array", pathData[uri])) {
return loadArray(pathData[uri]);
}
// 해당 URI의 데이터가 없을 경우
pathData[uri] = [];
_.ajax({
url: uri,
async: false,
success: function(xhr) {
var xml = xhr.responseXML,
svg = xml.getElementsByTagName("svg"),
style = xml.getElementsByTagName("style");
if(svg.length != 1) return;
var children = svg[0].childNodes;
for(var i = 0, len = children.length; i < len; i++) {
var elem = children[i],
name = elem.nodeName.toLowerCase();
if(elem.nodeType != 1) continue;
if(name == "g") {
pathData[uri] = pathData[uri].concat(getPathList(elem));
} else if(name == "path" || name == "polygon") {
var obj = {};
for(var key in elem.attributes) {
var attr = elem.attributes[key];
if(attr.specified && isLoadAttribute(attr.name)) {
obj[attr.name] = replaceXYValue(attr);
}
}
if(_.typeCheck("string", obj.id)) {
_.extend(obj, getDataById(obj.id));
}
pathData[uri].push(obj);
}
}
// 스타일 태그가 정의되어 있을 경우
for(var i = 0; i < style.length; i++) {
self.svg.root.element.appendChild(style[i]);
}
},
fail: function(xhr) {
throw new Error("JUI_CRITICAL_ERR: Failed to load resource");
}
});
return loadArray(pathData[uri]);
}
function isLoadAttribute(name) {
return (
name == "group" || name == "id" || name == "title" || name == "x" || name == "y" ||
name == "d" || name == "points" || name == "class" || name == "style"
);
}
function replaceXYValue(attr) {
if(attr.name == "x" || attr.name == "y") {
return parseFloat(attr.value);
}
return attr.value;
}
function getDataById(id) {
var list = self.axis.data;
for(var i = 0; i < list.length; i++) {
var dataId = self.axis.getValue(list[i], "id", null);
if(dataId == id) {
return list[i];
}
}
return null;
}
function makePathGroup() {
var group = self.chart.svg.group(),
list = loadPath(self.map.path);
for(var i = 0, len = list.length; i < len; i++) {
var path = list[i].path,
data = list[i].data;
//addEvent(path, list[i]);
group.append(path);
if(_.typeCheck("string", data.id)) {
pathIndex[data.id] = list[i];
}
}
return group;
}
function getScaleXY() { // 차후에 공통 함수로 변경해야 함
var w = self.map.width,
h = self.map.height,
px = ((w * pathScale) - w) / 2,
py = ((h * pathScale) - h) / 2;
return {
x: px + pathX,
y: py + pathY
}
}
function addEvent(elem, obj) {
var chart = self.chart;
elem.on("click", function(e) {
setMouseEvent(e);
chart.emit("map.click", [ obj, e ]);
});
elem.on("dblclick", function(e) {
setMouseEvent(e);
chart.emit("map.dblclick", [ obj, e ]);
});
elem.on("contextmenu", function(e) {
setMouseEvent(e);
chart.emit("map.rclick", [ obj, e ]);
e.preventDefault();
});
elem.on("mouseover", function(e) {
setMouseEvent(e);
chart.emit("map.mouseover", [ obj, e ]);
});
elem.on("mouseout", function(e) {
setMouseEvent(e);
chart.emit("map.mouseout", [ obj, e ]);
});
elem.on("mousemove", function(e) {
setMouseEvent(e);
chart.emit("map.mousemove", [ obj, e ]);
});
elem.on("mousedown", function(e) {
setMouseEvent(e);
chart.emit("map.mousedown", [ obj, e ]);
});
elem.on("mouseup", function(e) {
setMouseEvent(e);
chart.emit("map.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");
}
}
this.scale = function(id) {
if(!_.typeCheck("string", id)) return;
var x = null,
y = null,
path = null,
data = null,
pxy = getScaleXY();
if(_.typeCheck("object", pathIndex[id])) {
path = pathIndex[id].path;
data = pathIndex[id].data;
if(data.x != null) {
var dx = self.axis.getValue(data, "dx", 0),
cx = parseFloat(data.x) + dx;
x = (cx * pathScale) - pxy.x;
}
if(data.y != null) {
var dy = self.axis.getValue(data, "dy", 0),
cy = parseFloat(data.y) + dy;
y = (cy * pathScale) - pxy.y;
}
}
return {
x: x,
y: y,
path: path,
data: data
}
}
this.scale.each = function(callback) {
var self = this;
for(var id in pathIndex) {
callback.apply(self, [ id, pathIndex[id] ]);
}
}
this.scale.size = function() {
return {
width: self.map.width,
height: self.map.height
}
}
this.scale.scale = function(scale) {
if(!scale || scale < 0) return pathScale;
pathScale = scale;
pathGroup.scale(pathScale);
this.view(pathX, pathY);
return pathScale;
}
this.scale.view = function(x, y) {
var xy = { x: pathX, y: pathY };
if(!_.typeCheck("number", x) || !_.typeCheck("number", y))
return xy;
pathX = x;
pathY = y;
var pxy = getScaleXY();
pathGroup.translate(-pxy.x, -pxy.y);
return {
x: pathX,
y: pathY
}
}
this.draw = function() {
var root = this.chart.svg.group();
pathScale = this.map.scale;
pathX = this.map.viewX;
pathY = this.map.viewY;
pathGroup = makePathGroup();
// pathGroup 루트에 추가
root.append(pathGroup);
if(this.map.scale != 1) {
this.scale.scale(pathScale);
}
if(this.map.viewX != 0 || this.map.viewY != 0) {
this.scale.view(pathX, pathY);
}
if(this.map.hide) {
root.attr({ visibility: "hidden" });
}
return {
root: root,
scale: this.scale
};
}
this.drawAfter = function(obj) {
obj.root.attr({ "clip-path": "url(#" + this.axis.get("clipRectId") + ")" });
// 모든 path가 그려진 이후에 이벤트 설정
setTimeout(function() {
self.scale.each(function(id, obj) {
addEvent(obj.path, obj);
});
}, 1);
}
}
Map.setup = function() {
/** @property {chart.builder} chart */
/** @property {chart.axis} axis */
/** @property {Object} map */
return {
scale: 1,
viewX: 0,
viewY: 0,
/** @cfg {Boolean} [hide=false] Determines whether to display an applicable grid. */
hide: false,
/** @cfg {String} [map=''] Set a map file's name */
path: "",
/** @cfg {Number} [width=-1] Set map's width */
width: -1,
/** @cfg {Number} [height=-1] Set map's height */
height: -1
};
}
/**
* @event map_click
* Event that occurs when clicking on the map area. (real name ``` map.click ```)
* @param {jQueryEvent} e The event object.
* @param {Number} index Axis index.
*/
/**
* @event map_dblclick
* Event that occurs when double clicking on the map area. (real name ``` map.dblclick ```)
* @param {jQueryEvent} e The event object.
* @param {Number} index Axis index.
*/
/**
* @event map_rclick
* Event that occurs when right clicking on the map area. (real name ``` map.rclick ```)
* @param {jQueryEvent} e The event object.
* @param {Number} index Axis index.
*/
/**
* @event map_mouseover
* Event that occurs when placing the mouse over the map area. (real name ``` map.mouseover ```)
* @param {jQueryEvent} e The event object.
* @param {Number} index Axis index.
*/
/**
* @event map_mouseout
* Event that occurs when moving the mouse out of the map area. (real name ``` map.mouseout ```)
* @param {jQueryEvent} e The event object.
* @param {Number} index Axis index.
*/
/**
* @event map_mousemove
* Event that occurs when moving the mouse over the map area. (real name ``` map.mousemove ```)
* @param {jQueryEvent} e The event object.
* @param {Number} index Axis index.
*/
/**
* @event map_mousedown
* Event that occurs when left clicking on the map area. (real name ``` map.mousedown ```)
* @param {jQueryEvent} e The event object.
* @param {Number} index Axis index.
*/
/**
* @event map_mouseup
* Event that occurs after left clicking on the map area. (real name ``` map.mouseup ```)
* @param {jQueryEvent} e The event object.
* @param {Number} index Axis index.
*/
return Map;
}, "chart.draw");