제목이 참 마땅히 지을만한것도 없고.. 어여 이 곳에 정리해 내 머리속에서 지워버리고자 하는 마음에 지은 제목입니다.
상황은.. jetty를 사용해 지도 OpenAPI를 만들었고, 이 OpenAPI를 Spring 프레임워크를 기본으로 하는 웹 시스템에서 사용하는데, AJAX의 POST 방식으로 OpenAPI를 호출합니다. 이때 이 OpenAPI를 Spring 프레임워크가 아닌 환경에서는 Cross Domain 문제가 발생하지 않습니다. 물론 발생하지 않도록 OpenAPI 서버에 조치를 해두었습니다. 그런데.. Spring 프레임워크에서는 Cross Domain 에러가 발생합니다. 신기한건.. AJAX의 GET 방식은 Cross Domain 에러가 발생하지 않고 POST 만 발생합니다.
먼저 Cross Domain 문제는 아래의 코드를 OpenAPI 서버(jetty를 활용하는 서버)에 적용하여 해결할 수 있었습니다. 코드가 긴데.. 불필요한 코드가 있을거라 생각되지만 일단 모두 기재합니다.
FilterHolder holder = new FilterHolder(CrossOriginFilter.class);
holder.setInitParameter(CrossOriginFilter.ALLOWED_ORIGINS_PARAM, "*");
holder.setInitParameter(CrossOriginFilter.ACCESS_CONTROL_ALLOW_ORIGIN_HEADER, "*");
holder.setInitParameter(CrossOriginFilter.ALLOWED_METHODS_PARAM, "GET,POST,HEAD");
holder.setInitParameter(CrossOriginFilter.ALLOWED_HEADERS_PARAM, "X-Requested-With,Content-Type,Accept,Origin");
holder.setName("cross-origin");
FilterMapping fm = new FilterMapping();
fm.setFilterName("cross-origin");
fm.setPathSpec("*");
handler.addFilter(holder, fm);
위의 코드를 OpenAPI 서버에 적용하면 더 이상 Cross Domain 에러가 발생하지 않습니다만, 여전이 문제가 있습니다. POST 방식으로 데이터를 전달 하는데.. 이 데이터의 형식이 일반적으로 흔한 FORM 형태로 전달되어야 합니다. 그런데 Spring에서는 payload라는 형식으로 전달됩니다. 이에 대한 문제는 클라이언트 웹단에서 AJAX 호출시 headers 옵션값을 추가적으로 지정해 주면 해결됩니다. 즉, 아래처럼요.
솔찍히 Spring 프레임워크에서 발생하는 문제인지는 모르겠습니다. 단지 Spring 프레임워크가 아닌 환경에서는 문제가 발생하지 않았으므로, 아마도 Spring 프레임워크 환경에서 발생하는 문제라고 추측할 뿐이지만.. 여튼 이와 유사한 문제가 발생할 경우에 대한 해결책으로써 기록을 남깁니다.
JavaScript 언어를 이용해 SVG에 PieChart를 위한 도형을 생성하기 위한 코드를 함수로 정리해 둡니다. 최종 결과는 아래처럼 4개의 항목으로 구성된 가운데 구멍이 뚫린 모양입니다.
먼저 아래처럼 svg와 PieChart를 구성하는 4개의 항목을 path로 지정해 둡니다. (실제 활용시에는 이 부분을 모두 JavaScript 코드로 동적 생성하도록 해야 합니다)
위의 4개의 path에 대해 각각 파이차트의 구성 요소로 만들어 주는 함수는 아래와 같습니다.
function toPieChartItemPath(x, y, radiusIn, radiusOut, startAngle, endAngle) {
function _toXY(cX, cY, r, degrees) {
var rad = (degrees) * Math.PI / 180.0;
return {
x: cX + (r * Math.cos(rad)),
y: cY + (r * Math.sin(rad))
};
}
var startIn = _toXY(x, y, radiusIn, endAngle);
var endIn = _toXY(x, y, radiusIn, startAngle);
var startOut = _toXY(x, y, radiusOut, endAngle);
var endOut = _toXY(x, y, radiusOut, startAngle);
var arcSweep = (endAngle - startAngle) <= 180 ? "0" : "1";
var d = [
"M", startIn.x, startIn.y,
"L", startOut.x, startOut.y,
"A", radiusOut, radiusOut, 0, arcSweep, 0, endOut.x, endOut.y,
"L", endIn.x, endIn.y,
"A", radiusIn, radiusIn, 0, arcSweep, 1, startIn.x, startIn.y,
"z"
].join(" ");
return d;
}
이 함수의 인자를 설명하면, x와 y는 파이 차트의 중심점이고 radiusIn과 radiusOut은 파이차트의 내부 반지름과 외부 반지름 값입니다. 그리고 startAngle와 endAngle는 시작각도(3시 방향이 0도)와 종료각도(시계방향)입니다. 이 함수를 통해 앞서 구성한 svg와 path 요소를 파이차트로 구성하는 코드는 아래와 같습니다.
“소프트웨어 개발이 좋은 사람”이라는 블로그에서 d3를 이용해 아날로그 시계를 만든 글을 보고 코드를 살펴 보았습니다. d3는 개인적으로도 관심이 많고, 상당이 깊이 있게 공부를 했던(하지만 활용력은 아직까지도 얇은..) 자바스크립트 기반의 데이터 시각화 라이브러리입니다. 그 결과는 아래와 같습니다.
[xyz-ihs snippet=”D3-Clock”]
위의 시계에 대한 코드를 d3의 학습겸 해서 정리해 보고자 합니다. 먼저 시계에 대한 뷰는 아래처럼 오직 svg 태그 하나입니다.
시계의 크기에 해당하는 이 svg의 크기를 지정하기 위해 다음처럼 스타일을 지정합니다.
#clock {
width: 400px;
height: 400px;
}
시계의 구성요소는 시계의 형태인 큰 동그라미 부분, 12개의 숫자 부분, 시분초에 대한 바늘 부분으로 구성됩니다. 시분초에 대한 바늘은 Timer를 사용해 1초마다 갱신해 진짜 시계처럼 움직이도록 합니다.
스크립트 코드를 작성해 볼 것인데요. 가장 먼저 앞서 추가해둔 svg 요소를 d3의 선택자를 이용해 선택해 둡니다.
var clock = d3.select("#clock");
var center = parseInt(clock.style("height")) / 2;
var radius = center;
위의 코드는 추가적으로 svg의 높이값의 반값으로 중심점을 위한 center 변수와 시계의 반지름 크기값을 위한 radius 변수도 정의되어 있습니다.
앞서 시계는 3개의 부분으로 구성된다고 하였는데요. 다시 언급하면 시계의 형태인 큰 동그라미 부분, 12개의 숫자 부분, 시분초에 대한 바늘 부분입니다. 먼저 시계의 형태인 큰 동그라미 부분을 그리는 함수 drawFace를 반지름값(radius 변수)을 인자로 해 호출합니다.
drawFace(radius);
function drawFace(radius) {
clock.append("circle")
.attr("cx", center)
.attr("cy", center)
.attr("r", radius)
.attr("class", "face");
clock.append("circle")
.attr({
"cx": center,
"cy": center,
"r": radius * 0.1,
"fill": "#000"
});
}
위의 코드에서 face라는 class를 통해 스타일을 지정하고 있는데요. 이 .face에 대한 스타일 정의는 아래와 같습니다.
.face {
fill : #FFF;
stroke-width: 2px;
stroke: #000;
}
또 아래는 12개의 숫자 부분을 구성하는 코드입니다.
drawNumbers(radius);
function drawNumbers(radius) {
var pos = radius * 0.85;
for (var num = 1; num < 13; num++) {
var deg = 30 * num;
var x = pos * Math.cos(Math.PI * (deg - 90) / 180);
var y = pos * Math.sin(Math.PI * (deg - 90) / 180);
var cx = x + center;
var cy = y + center;
var text = clock.append("text")
.attr({ "x": cx, "y": cy, "class": "number" })
.text(num)
.style("font-size", radius * 0.15 + "px")
.attr("transform", "rotate(" + deg + ", " + cx + ", " + cy + ")");
}
}
위의 코드에서 number라는 class를 통해 숫자의 문자 스타일을 지정하고 있는데요. 이 .number에 대한 스타일 정의는 아래와 같습니다.
또 아래는 시, 분, 초에 대한 바늘을 구성하는 코드입니다. 시침, 분침, 초침을 각각 hourHand, minuteHand, secondHand 변수에 저장하고 있는데요. 이 변수를 통해 1초마다 각 침의 형태를 변경해 주게 됩니다.
var hourHand = drawHand(0, 0, radius*0.07);
var minuteHand = drawHand(0, 0, radius*0.05);
var secondHand = drawHand(0, 0, radius*0.01);
function drawHand(x, y, width) {
var hand = clock.append("line")
.attr({
"x1": center,
"y1": center,
"x2": x + center,
"y2": y + center,
"class": "hands" })
.style("stroke-width", width);
return hand;
}
위의 코드에서 hand라는 class를 통해 바늘의 스타일을 정의하고 있습니다. 이 .hand에 대한 스타일 정의는 아래와 같습니다.
.hands {
stroke: #000;
stroke-linecap: round;
}
이제 1초마다 제 시간에 맞게 바늘을 움직도록 하는 코드는 아래와 같습니다.
drawClock();
function drawClock() {
drawTime(radius);
setTimeout(drawClock, 1000);
}
위의 코드에서 1초마다 실행되는 drawTime의 함수가 보이는데요. 바로 이 drawTime이 3개의 바늘을 현재 시간에 맞게 그 형상을 변경해 주는 함수이며 아래와 같습니다.
function drawTime(radius){
var now = new Date();
var hour = now.getHours();
var minute = now.getMinutes();
var second = now.getSeconds();
//hour
var pos = radius * 0.5;
x = pos * Math.cos(Math.PI*((hour*30)-90+30/60*minute+30/60/60*second)/180);
y = pos * Math.sin(Math.PI*((hour*30)-90+30/60*minute+30/60/60*second)/180);
hourHand.attr({"x1": center, "y1": center, "x2": x+center, "y2": y+center});
//minute
pos = radius*0.65;
x = pos * Math.cos(Math.PI*((minute*6)-90+6/60*second)/180);
y = pos * Math.sin(Math.PI*((minute*6)-90+6/60*second)/180);
minuteHand.attr({"x1": center, "y1": center, "x2": x+center, "y2": y+center});
// second
pos = radius*0.9;
x = pos * Math.cos(Math.PI* ((second*6)-90)/180);
y = pos * Math.sin(Math.PI* ((second*6)-90)/180);
secondHand.attr({"x1": center, "y1": center, "x2": x+center, "y2": y+center});
}
자바에서 제공하는 컨테이너(Container) 중 어떤 데이터에 대해 우선순위 값을 부여하고, 이 우선순위를 기준으로 자동으로 정렬되어, 우선순위에 따라 데이터를 꺼내어 사용할 수 있는 우선순위 큐에 대한 예제 코드를 정리합니다.
먼저 우선순위 값을 갖는 데이터에 대한 타입 정의가 필요합니다. 아래처럼 Node라는 클래스를 추가해 타입을 정의합니다.
package tstPriorityQueue;
public class Node implements Comparable<Node> {
private String UUID;
private String parentUUID;
private double G;
private double H;
public Node(String UUID, double G, double H) {
this.UUID = UUID;
this.parentUUID = null;
this.G = G;
this.H = H;
}
public double getF() { return G + H; }
public double getG() { return G; }
public double getH() { return H; }
public String getNode() { return UUID; }
public String getParentNode() { return parentUUID; }
public void setG(double v) { G = v; }
public void setH(double v) { H = v; }
public void setParentNode(String v) { parentUUID = v; }
@Override
public int compareTo(Node target) {
if (this.getF() > target.getF()) {
return 1;
} else if (this.getF() < target.getF()) {
return -1;
}
return 0;
}
public String toString() {
return UUID + '(' + getF() + ')';
}
}
위의 클래스에서 중요한 부분은 우선순위값을 얻기 위한 getF() 함수입니다. 이 함수는 데이터의 상대적인 크기의 비교를 위한 인터페이스인 Comparable 구현할 때 사용되는 함수인데요. 바로 compareTo 라는 함수로써, 위의 경우에는 우선순위값이 작은 것을 먼저 꺼내어 사용하겠다는 정의입니다.
실제로, 위의 Node 클래스에 대한 타입으로 정의된 데이터를 컨테이너에 넣고, 사용하는 코드는 아래와 같습니다.
package tstPriorityQueue;
import java.util.PriorityQueue;
public class EntryMain {
public static void main(String[] args) {
// Create items
Node node1 = new Node("423182c4-edb5-11e6-bc64-92361f002671", 1.0, 5.1);
Node node2 = new Node("42318742-edb5-11e6-bc64-92361f002671", 1.0, 2.4);
Node node3 = new Node("42318878-edb5-11e6-bc64-92361f002671", 1.0, 3.8);
Node node4 = new Node("42318968-edb5-11e6-bc64-92361f002671", 1.0, 6.2);
Node node5 = new Node("42318a3a-edb5-11e6-bc64-92361f002671", 1.0, 4.5);
// Create priority queue
PriorityQueue<Node> pQueue = new PriorityQueue<Node>();
// Add items to queue
pQueue.offer(node1); // same code as pQueue.add(node1)
pQueue.offer(node2);
pQueue.offer(node3);
pQueue.offer(node4);
pQueue.offer(node5);
// Get items from queue
while(!pQueue.isEmpty()) {
Node node = pQueue.poll();
System.out.println(node);
}
}
}
데이터를 5개 생성해서, 우선순위 큐 저장소에 저장하고 최종적으로 26번 코드를 통해 5개의 데이터를 우선순위에 따라 꺼내어 화면에 표시합니다. 그 결과는 아래와 같습니다.