/**
|
* Created by wisestone on 2018-11-13.
|
*/
|
'use strict';
|
|
define(['app', 'angular', 'd3'],
|
function (app, angular, d3) {
|
app.directive('jsWorkflowDisabled', ["$log", "$rootScope",
|
function ($log, $rootScope) {
|
return {
|
scope : {
|
issueStatusList : '=issueStatusList',
|
vm : '=ngModel'
|
},
|
restrict : 'E',
|
replace : true,
|
templateUrl : 'custom_components/js-workflow/js-workflow-disabled.html',
|
controller : function ($scope, $element, $attrs) {
|
|
// 함수 모음
|
$scope.fn = {
|
initializeLayout : initializeLayout,
|
makeNodeAndLink : makeNodeAndLink,
|
forceLayout : forceLayout,
|
initCircleColor : initCircleColor,
|
initLinkColor : initLinkColor,
|
makeNode : makeNode,
|
makeLink : makeLink,
|
makeGridLine : makeGridLine,
|
executeForce : executeForce,
|
setLink : setLink,
|
setNode : setNode,
|
setNodeTexts : setNodeTexts,
|
targetClick : targetClick,
|
tick : tick,
|
startDiagram : startDiagram,
|
setLastStatusPosition : setLastStatusPosition,
|
setLastTransitionPosition : setLastTransitionPosition,
|
updateClientChangePosition : updateClientChangePosition,
|
};
|
|
// 변수 모음
|
$scope.vm.targetIssueStatusList = [];
|
$scope.vm.issueStatuses = []; // 선택 가능한 이슈 상태 목록
|
$scope.vm.addIssueStatusId = null; // 추가하는 이슈 상태 아이디
|
$scope.vm.width = 0; // 넓이
|
$scope.vm.height = 0; // 높이
|
$scope.vm.svg = null; // svg 정보
|
$scope.vm.rect = null; // 다이어그램이 그려지는 사각형 정보
|
$scope.vm.nodes = []; // 원 정보 모음
|
$scope.vm.links = []; // 링크 정보 모음
|
$scope.vm.activeTarget = null; // 현재 선택한 대상 정보
|
$scope.vm.drag = null;
|
$scope.vm.force = null;
|
$scope.vm.node = null;
|
$scope.vm.link = null;
|
$scope.vm.nodeTexts = null; // 원에 표시되는 텍스트
|
$scope.vm.sourceStatusId = null; // 출발하는 이슈 상태 아이디
|
$scope.vm.targetStatusId = null; // 연결하는 이슈 상태 아이디
|
$scope.vm.transitionIdGenerator = 100; // 전이선 임시 id 값
|
|
|
$scope.$watch("issueStatusList", function (newValue) {
|
if ($rootScope.isDefined(newValue)) {
|
$scope.fn.startDiagram();
|
}
|
});
|
|
// 다이어그램을 그리기 시작한다.
|
function startDiagram() {
|
$("#svgContent").empty();
|
|
$scope.vm.width = $(".workflowbox").width(); // 넓이
|
$scope.vm.height = $(".workflowbox").height(); // 높이
|
|
$scope.vm.activeTarget = null; // 타겟 초기화
|
$scope.fn.initCircleColor(null, true); // 마지막 선택 노드 색상 초기화
|
$scope.fn.forceLayout(); // 다이어그램 에디터 그리기 시작
|
}
|
|
// 상태 마지막 위치 정보 저장
|
function setLastStatusPosition(issueStatus) {
|
issueStatus.xLocation = $scope.vm.nodes[issueStatus.name].x;
|
issueStatus.yLocation = $scope.vm.nodes[issueStatus.name].y;
|
}
|
|
// 전이선 마지막 위치 정보 저장
|
function setLastTransitionPosition(workflowTransition, index) {
|
workflowTransition.correctX = $scope.vm.links[index].correctX;
|
workflowTransition.correctY = $scope.vm.links[index].correctY;
|
workflowTransition.direct = $scope.vm.links[index].direct;
|
}
|
|
|
// 클라이언트에서 변경한 위치 정보를 클라이언트에서 관리하는 데이터에 업데이트한다.
|
function updateClientChangePosition() {
|
// 서버에서 내려온 데이터에 전이선 변경 정보를 업데이트한다.
|
angular.forEach($scope.vm.issueStatusVos, function (issueStatusVo) {
|
angular.forEach(issueStatusVo.workflowTransitionVos, function (workflowTransition) {
|
for (var count in $scope.vm.links) {
|
if ($scope.vm.links[count].id == workflowTransition.id) {
|
$scope.fn.setLastTransitionPosition(workflowTransition, count);
|
break;
|
}
|
}
|
});
|
|
$scope.fn.setLastStatusPosition(issueStatusVo);
|
});
|
}
|
|
// 노드 색상을 초기화한다.
|
function initCircleColor(target, all) {
|
d3.selectAll("circle").each(function () {
|
var circle = d3.select(this)[0][0]["__data__"];
|
|
if (all) {
|
d3.select(this).style("fill", circle.originalColor);
|
}
|
else {
|
if (circle.id != target.id) {
|
d3.select(this).style("fill", circle.originalColor);
|
}
|
}
|
});
|
}
|
|
// 전이선 색상을 초기화한다.
|
function initLinkColor(target, all) {
|
d3.selectAll(".link").each(function () {
|
var link = d3.select(this)[0][0]["__data__"];
|
|
// 전체 초기화일경우
|
if (all) {
|
d3.select(this).classed("linkGreen", false);
|
d3.select(this).classed(link.colorClass, true);
|
}
|
else {
|
if (link.id != target.id) {
|
d3.select(this).classed("linkGreen", false);
|
d3.select(this).classed(link.colorClass, true);
|
}
|
}
|
|
d3.select(this).attr("marker-end", "url(#licensing)");
|
});
|
}
|
|
// 타겟을 클릭했을 때 색상을 변경한다.
|
function targetClick(target) {
|
$scope.vm.activeTarget = angular.copy(target[0][0]["__data__"]);
|
|
// node 일경우
|
if ($scope.vm.activeTarget.type == "01") {
|
// 노드, 링크 전체 초기화
|
$scope.fn.initCircleColor($scope.vm.activeTarget, false);
|
$scope.fn.initLinkColor(null, true);
|
|
// 대상 노드 색 변경
|
target.style("fill", "#1DDB16");
|
}
|
else if ($scope.vm.activeTarget.type == "02") {
|
// 노드, 링크 전체 초기화
|
$scope.fn.initCircleColor(null, true);
|
$scope.fn.initLinkColor($scope.vm.activeTarget, false);
|
// 대상 링크 색 변경
|
target.classed("linkGreen", true);
|
target.classed($scope.vm.activeTarget.colorClass, false);
|
target.attr("marker-end", "url(#greenLicensing)");
|
}
|
|
if ($scope.$root.$$phase != '$apply' && $scope.$root.$$phase != '$digest') {
|
$scope.$apply();
|
}
|
}
|
|
// 다이어그램에서 도형을 움직일 때 실행
|
function tick() {
|
//IE mark bug fix
|
$scope.vm.link.each(function () {
|
this.parentNode.insertBefore(this, this);
|
});
|
|
$scope.vm.link.attr("d", function (d) {
|
//svg 영역 내에 표시
|
if (d.target.x > $scope.vm.width) {
|
d.target.x = ($scope.vm.width - 31);
|
}
|
else if (d.target.x < 30) {
|
d.target.x = 30;
|
}
|
|
if (d.source.x > $scope.vm.width) {
|
d.source.x = ($scope.vm.width - 31);
|
}
|
else if (d.source.x < 30) {
|
d.source.x = 30;
|
}
|
|
if (d.target.y > $scope.vm.height) {
|
d.target.y = ($scope.vm.height - 31);
|
}
|
else if (d.target.y < 30) {
|
d.target.y = 30;
|
}
|
|
if (d.source.y > $scope.vm.height) {
|
d.source.y = ($scope.vm.height - 31);
|
}
|
else if (d.source.y < 30) {
|
d.source.y = 30;
|
}
|
|
if (d.correctX == null) {
|
d.correctX = d.source.x;
|
}
|
|
if (d.correctY == null) {
|
d.correctY = d.source.y;
|
}
|
|
// 직선화 유지 상태 일 경우
|
if (d.direct) {
|
d.correctX = d.source.x;
|
d.correctY = d.source.y;
|
}
|
|
return "M" + d.source.x + "," + d.source.y + " Q" + d.correctX + ", " + d.correctY + " " + d.target.x + ", " + d.target.y;
|
});
|
|
$scope.vm.node.attr("transform", function (d) {
|
if (d.x > $scope.vm.width) {
|
d.x = ($scope.vm.width - 31);
|
}
|
else if (d.x < 30) {
|
d.x = 30;
|
}
|
|
if (d.y > $scope.vm.height) {
|
d.y = ($scope.vm.height - 31);
|
}
|
else if (d.y < 30) {
|
d.y = 30;
|
}
|
|
return "translate(" + d.x + "," + d.y + ")";
|
});
|
|
$scope.vm.nodeTexts.attr("transform", function (d) {
|
return "translate(" + d.x + "," + d.y + ")";
|
});
|
|
// on 이벤트가 과다하게 발생하는 현상을 방지
|
$scope.vm.force.stop();
|
}
|
|
// 노드를 만든다.
|
function makeNode(issueStatus, sXLocation, sYLocation) {
|
var xLocation = 0;
|
var yLocation = 0;
|
var color = "FAED7D";
|
|
// x좌표
|
if (issueStatus.xLocation == 0 || issueStatus.xLocation == null) {
|
xLocation = sXLocation;
|
}
|
else {
|
xLocation = issueStatus.xLocation;
|
}
|
// y좌표
|
if (issueStatus.yLocation == 0 || issueStatus.yLocation == null) {
|
yLocation = sYLocation;
|
}
|
else {
|
yLocation = issueStatus.yLocation;
|
}
|
|
if (issueStatus.color != null) {
|
color = issueStatus.color;
|
}
|
|
return {
|
id : issueStatus.id,
|
name : issueStatus.name,
|
x : xLocation,
|
y : yLocation,
|
color : color,
|
issueStatusType : issueStatus.issueStatusType,
|
originalColor : issueStatus.color,
|
fixed : true,
|
type : "01", // 수정 모드시 구분
|
issueStatus : issueStatus
|
}
|
}
|
|
// 전이선을 만든다.
|
function makeLink(workflowTransition) {
|
return {
|
id : workflowTransition.id,
|
source : workflowTransition.sourceStatusName,
|
target : workflowTransition.targetStatusName,
|
correctX : workflowTransition.correctX,
|
correctY : workflowTransition.correctY,
|
direct : workflowTransition.direct,
|
type : "02",
|
colorClass : "linkBlue",
|
workflowTransition : workflowTransition
|
}
|
}
|
|
|
// 1. 서버에서 데이터가 내려올때 사용된 이슈 상태 전체 목록과 각 이슈 상태의 전이선 정보가 내려와야 한다.
|
|
// 노드와 링크 정보를 생성한다.
|
function makeNodeAndLink() {
|
$scope.vm.nodes = []; // 원 정보 모음
|
$scope.vm.links = []; // 링크 정보 모음
|
|
var startX = 50; // 새로운 도형의 X축 위치
|
var startY = 50; // 새로운 도형의 Y축 위치
|
|
// 노드와 링크 정보를 만든다.
|
angular.forEach($scope.vm.issueStatusVos, function (issueStatusVo, index) {
|
$scope.vm.nodes[issueStatusVo.name] = $scope.fn.makeNode(issueStatusVo, startX, startY);
|
|
angular.forEach(issueStatusVo.workflowTransitionVos, function (workflowTransition) {
|
$scope.vm.links.push($scope.fn.makeLink(workflowTransition));
|
});
|
|
// 기본 좌표가 없을 경우 계속 60씩 증가
|
startX += 60;
|
|
if (index % 2 == 0) {
|
startY += 30;
|
}
|
else {
|
startY += 30;
|
}
|
});
|
|
angular.forEach($scope.vm.links, function (link) {
|
if (link.source == $scope.vm.nodes[link.source].name) {
|
link.source = $scope.vm.nodes[link.source];
|
}
|
|
if (link.target == $scope.vm.nodes[link.target].name) {
|
link.target = $scope.vm.nodes[link.target];
|
}
|
});
|
}
|
|
// 기초 레이아웃을 설정한다.
|
function initializeLayout() {
|
$scope.vm.svg = d3.select("#svgContent")
|
.append("svg")
|
.attr("width", $scope.vm.width)
|
.attr("height", $scope.vm.height);
|
|
$scope.vm.rect = $scope.vm.svg.append("rect")
|
.attr("width", $scope.vm.width)
|
.attr("height", $scope.vm.height)
|
.attr("fill", "white")
|
.on("click", function (d) {
|
// 원 색상 초기화
|
$scope.fn.initCircleColor(null, true);
|
// 링크 색상 초기화
|
$scope.fn.initLinkColor(null, true);
|
// 현재 선택한 대상 정보
|
$scope.vm.activeTarget = null;
|
|
if ($scope.$root.$$phase != '$apply' && $scope.$root.$$phase != '$digest') {
|
$scope.$apply();
|
}
|
});
|
}
|
|
|
// 표시되는 선을 생성한다.
|
function makeGridLine() {
|
// 기본 표시되는 선
|
$scope.vm.svg.append("marker")
|
.attr("orient", "auto")
|
.attr("markerHeight", 4)
|
.attr("markerWidth", 4)
|
.attr("refX", 18)
|
.attr("viewBox", "0 -5 10 10")
|
.attr("id", "licensing")
|
.append("path")
|
.attr("d", function () {
|
return "M0,-5 L10,0 L0,5";
|
});
|
|
// 선을 눌러서 활성화되었을때
|
$scope.vm.svg.append("marker")
|
.attr("orient", "auto")
|
.attr("markerHeight", 5)
|
.attr("markerWidth", 5)
|
.attr("refX", 16)
|
.attr("viewBox", "0 -5 10 10")
|
.attr("id", "greenLicensing")
|
.append("path")
|
.attr("d", function () {
|
return "M0,-5 L10,0 L0,5";
|
});
|
}
|
|
// 포스 레이아웃 실행
|
function executeForce() {
|
$scope.vm.force = d3.layout.force()
|
.nodes(d3.values($scope.vm.nodes))
|
.links($scope.vm.links)
|
.size([$scope.vm.width, $scope.vm.height])
|
.on("tick", $scope.fn.tick)
|
.start();
|
}
|
|
// 링크를 셋팅한다.
|
function setLink() {
|
$scope.vm.link = $scope.vm.svg.append("svg:g")
|
.selectAll(".link")
|
.data($scope.vm.force.links())
|
.enter()
|
.append("path")
|
.attr("class", function (d) {
|
return "link " + d.colorClass;
|
})
|
.attr("id", function (d, i) {
|
return "link_" + i;
|
})
|
.attr("marker-end", function (d) {
|
return "url(#licensing)";
|
})
|
.attr("cursor", "pointer")
|
.on("click", function () {
|
$scope.fn.targetClick(d3.select(this));
|
});
|
}
|
|
// 노드를 셋팅한다.
|
function setNode() {
|
$scope.vm.node = $scope.vm.svg.append("svg:g")
|
.selectAll("circle")
|
.data($scope.vm.force.nodes())
|
.enter()
|
.append("circle")
|
.attr("class", "node")
|
.style("fill", function (d) {
|
return d.color;
|
})
|
.style("cursor", "pointer")
|
.attr("r", 15)
|
.on("click", function () {
|
$scope.fn.targetClick(d3.select(this));
|
});
|
}
|
|
// 노드 텍스트를 셋팅한다.
|
function setNodeTexts() {
|
$scope.vm.nodeTexts = $scope.vm.svg.append("svg:g")
|
.selectAll("text")
|
.data($scope.vm.force.nodes())
|
.enter()
|
.append("text")
|
.attr("class", "label")
|
.attr("fill", "black")
|
.attr("dy", 30)
|
.style("font-size", "12px")
|
.style("text-anchor", "middle")
|
.text(function (d) {
|
var name = "";
|
if (d.name != null) {
|
if (d.name.length > 10) {
|
name = d.name.substring(0, 10) + "...";
|
}
|
else {
|
name = d.name;
|
}
|
}
|
|
return name;
|
});
|
}
|
|
// 워크플로우 다이어그램을 그린다.
|
function forceLayout() {
|
$scope.fn.initializeLayout($scope.vm.width, $scope.vm.height);
|
$scope.fn.makeNodeAndLink();
|
$scope.fn.makeGridLine();
|
$scope.fn.executeForce($scope.vm.width, $scope.vm.height);
|
$scope.fn.setLink();
|
$scope.fn.setNode();
|
$scope.fn.setNodeTexts();
|
|
}
|
},
|
link : function (scope, element, attrs) {
|
|
}
|
}
|
}])
|
});
|