///////////////////
//Page 전역 변수
///////////////////
var selectedPdServiceId;
var selectedVersionId;
var versionListData;
var globalDeadline;
// 최상단 메뉴 변수
var req_state, resource_info, issue_info, period_info, total_days_progress;
// 필요시 작성
////////////////////////////////////////////////////////////////////////////////////////
//Document Ready
////////////////////////////////////////////////////////////////////////////////////////
function execDocReady() {
var pluginGroups = [
[
"../reference/light-blue/lib/vendor/jquery.ui.widget.js",
"../reference/light-blue/lib/vendor/http_blueimp.github.io_JavaScript-Templates_js_tmpl.js",
"../reference/light-blue/lib/vendor/http_blueimp.github.io_JavaScript-Load-Image_js_load-image.js",
"../reference/light-blue/lib/vendor/http_blueimp.github.io_JavaScript-Canvas-to-Blob_js_canvas-to-blob.js",
"../reference/light-blue/lib/jquery.iframe-transport.js",
"../reference/lightblue4/docs/lib/slimScroll/jquery.slimscroll.min.js",
"../reference/jquery-plugins/unityping-0.1.0/dist/jquery.unityping.min.js",
"../reference/light-blue/lib/bootstrap-datepicker.js",
"../reference/jquery-plugins/datetimepicker-2.5.20/build/jquery.datetimepicker.min.css",
"../reference/jquery-plugins/datetimepicker-2.5.20/build/jquery.datetimepicker.full.min.js",
"../reference/lightblue4/docs/lib/widgster/widgster.js"
],
[
"../reference/jquery-plugins/select2-4.0.2/dist/css/select2_lightblue4.css",
"../reference/jquery-plugins/lou-multi-select-0.9.12/css/multiselect-lightblue4.css",
"../reference/jquery-plugins/multiple-select-1.5.2/dist/multiple-select-bluelight.css",
"../reference/jquery-plugins/select2-4.0.2/dist/js/select2.min.js",
"../reference/jquery-plugins/lou-multi-select-0.9.12/js/jquery.quicksearch.js",
"../reference/jquery-plugins/lou-multi-select-0.9.12/js/jquery.multi-select.js",
"../reference/jquery-plugins/multiple-select-1.5.2/dist/multiple-select.min.js"
],
[
"../reference/jquery-plugins/dataTables-1.10.16/media/css/jquery.dataTables_lightblue4.css",
"../reference/jquery-plugins/dataTables-1.10.16/extensions/Responsive/css/responsive.dataTables_lightblue4.css",
"../reference/jquery-plugins/dataTables-1.10.16/extensions/Select/css/select.dataTables_lightblue4.css",
"../reference/jquery-plugins/dataTables-1.10.16/media/js/jquery.dataTables.min.js",
"../reference/jquery-plugins/dataTables-1.10.16/extensions/Responsive/js/dataTables.responsive.min.js",
"../reference/jquery-plugins/dataTables-1.10.16/extensions/Select/js/dataTables.select.min.js",
"../reference/jquery-plugins/dataTables-1.10.16/extensions/RowGroup/js/dataTables.rowsGroup.min.js",
"../reference/jquery-plugins/dataTables-1.10.16/extensions/Buttons/js/dataTables.buttons.min.js",
"../reference/jquery-plugins/dataTables-1.10.16/extensions/Buttons/js/buttons.html5.js",
"../reference/jquery-plugins/dataTables-1.10.16/extensions/Buttons/js/buttons.print.js",
"../reference/jquery-plugins/dataTables-1.10.16/extensions/Buttons/js/jszip.min.js"
],
[
// echarts
"../reference/jquery-plugins/echarts-5.4.3/dist/echarts.min.js",
// d3(게이지 차트 사용)
"../reference/jquery-plugins/d3-5.16.0/d3.min.js",
// chart Colors
"./js/common/colorPalette.js",
// 최상단 메뉴
"./js/analysis/topmenu/topMenuApi.js",
"./js/common/chart/eCharts/basicRadar.js",
// 버전 timeline js, css
"./js/analysis/time/D_analysisTime.js",
"./js/analysis/time/timeline_analysisTime.js",
"./js/dashboard/chart/infographic_custom.css",
// 히트맵 사용 js, css
"./js/analysis/time/calendar_yearview_blocks_analysisTime.js",
"../reference/jquery-plugins/github-calendar-heatmap/css/calendar_yearview_blocks.css",
]
// 추가적인 플러그인 그룹들을 이곳에 추가하면 됩니다.
];
loadPluginGroupsParallelAndSequential(pluginGroups)
.then(function () {
console.log("모든 플러그인 로드 완료");
//vfs_fonts 파일이 커서 defer 처리 함.
setTimeout(function () {
var script = document.createElement("script");
script.src = "../reference/jquery-plugins/dataTables-1.10.16/extensions/Buttons/js/vfs_fonts.js";
script.defer = true; // defer 속성 설정
document.head.appendChild(script);
}, 5000); // 5초 후에 실행됩니다.
//pdfmake 파일이 커서 defer 처리 함.
setTimeout(function () {
var script = document.createElement("script");
script.src = "../reference/jquery-plugins/dataTables-1.10.16/extensions/Buttons/js/pdfmake.min.js";
script.defer = true; // defer 속성 설정
document.head.appendChild(script);
}, 5000); // 5초 후에 실행됩니다.
// 사이드 메뉴 처리
$(".widget").widgster();
setSideMenu("sidebar_menu_analysis", "sidebar_menu_analysis_time");
//제품(서비스) 셀렉트 박스 이니시에이터
makePdServiceSelectBox();
//버전 멀티 셀렉트 박스 이니시에이터
makeVersionMultiSelectBox();
// 높이 조정
$('.top-menu-div').matchHeight({
target: $('.top-menu-div-scope')
});
// candleStickChart();
})
.catch(function (error) {
console.error("플러그인 로드 중 오류 발생" + error);
});
}
///////////////////////
//제품 서비스 셀렉트 박스
//////////////////////
function makePdServiceSelectBox() {
//제품 서비스 셀렉트 박스 이니시에이터
$(".chzn-select").each(function () {
$(this).select2($(this).data());
});
//제품 서비스 셀렉트 박스 데이터 바인딩
$.ajax({
url: "/auth-user/api/arms/pdServicePure/getPdServiceMonitor.do",
type: "GET",
contentType: "application/json;charset=UTF-8",
dataType: "json",
progress: true,
statusCode: {
200: function (data) {
//////////////////////////////////////////////////////////
for (let k in data.response) {
let obj = data.response[k];
let newOption = new Option(obj.c_title, obj.c_id, false, false);
$("#selected_pdService").append(newOption).trigger("change");
}
}
}
});
$("#selected_pdService").on("select2:open", function () {
//슬림스크롤
makeSlimScroll(".select2-results__options");
});
// --- select2 ( 제품(서비스) 검색 및 선택 ) 이벤트 --- //
$("#selected_pdService").on("select2:select", function (e) {
// 제품( 서비스 ) 선택했으니까 자동으로 버전을 선택할 수 있게 유도
// 디폴트는 base version 을 선택하게 하고 ( select all )
//~> 이벤트 연계 함수 :: Version 표시 jsTree 빌드
dateTimePickerBinding();
dailyChartDataSearchEvent();
baseDateReset();
bindVersionDataByPdService();
console.log("[ analysisTime :: makePdServiceSelectBox ] :: 선택된 제품(서비스) c_id = " + $("#selected_pdService").val());
});
} // end makePdServiceSelectBox()
function bindVersionDataByPdService() {
$(".multiple-select option").remove();
$.ajax({
url: "/auth-user/api/arms/pdService/getVersionList?c_id=" + $("#selected_pdService").val(),
type: "GET",
dataType: "json",
progress: true,
statusCode: {
200: function (data) {
console.log("[ analysisTime :: bindVersionDataByPdService ] :: 선택된 버전 데이터 = ");
console.log(data.response); // versionData
versionListData = data.response.reduce((obj, item) => {
obj[item.c_id] = item;
return obj;
}, {});
let pdServiceVersionIds = [];
for (let k in data.response) {
let obj = data.response[k];
pdServiceVersionIds.push(obj.c_id);
let newOption = new Option(obj.c_title, obj.c_id, true, false);
$(".multiple-select").append(newOption);
}
selectedPdServiceId = $("#selected_pdService").val();
selectedVersionId = pdServiceVersionIds.join(",");
if (!selectedPdServiceId || selectedPdServiceId === null || selectedPdServiceId === undefined || selectedPdServiceId === "") {
return;
}
baseDateReset();
// 최상단 메뉴 세팅
TopMenuApi.톱메뉴_초기화();
TopMenuApi.톱메뉴_세팅();
// 버전 및 게이지차트, 버전 타임라인 차트 초기화
statisticsMonitor(selectedPdServiceId, selectedVersionId);
// 히트맵 차트 초기화
calendarHeatMap(selectedPdServiceId, selectedVersionId);
// 요구사항 및 연결된 이슈 생성 누적 개수 및 업데이트 상태 현황 멀티 스택바 차트
dailyUpdatedStatusScatterChart(selectedPdServiceId, selectedVersionId);
dailyCreatedCountAndUpdatedStatusesMultiStackCombinationChart(selectedPdServiceId, selectedVersionId);
timeLineChart(selectedPdServiceId, selectedVersionId);
if (data.length > 0) {
console.log("display 재설정.");
}
//$('#multiversion').multipleSelect('refresh');
//$('#edit_multi_version').multipleSelect('refresh');
$(".multiple-select").multipleSelect("refresh");
//////////////////////////////////////////////////////////
}
}
});
}
////////////////////
//버전 멀티 셀렉트 박스
////////////////////
function makeVersionMultiSelectBox() {
//버전 선택시 셀렉트 박스 이니시에이터
$(".multiple-select").multipleSelect({
filter: true,
onClose: function () {
console.log("[ analysisTime :: makeVersionMultiSelectBox ] :: onOpen event fire!\n");
let checked = $("#checkbox1").is(":checked");
let endPointUrl = "";
let versionTag = $(".multiple-select").val();
if (versionTag === null || versionTag == "") {
alert("버전이 선택되지 않았습니다.");
$(".ms-parent").css("z-index", 1000);
return;
}
selectedPdServiceId = $("#selected_pdService").val();
selectedVersionId = versionTag.join(",");
if (selectedPdServiceId === null || selectedPdServiceId === undefined || selectedPdServiceId === "") {
return;
}
baseDateReset();
// 최상단 메뉴 통계
TopMenuApi.톱메뉴_초기화();
TopMenuApi.톱메뉴_세팅();
// 버전 및 게이지차트, 버전 타임라인 차트 초기화
statisticsMonitor(selectedPdServiceId, selectedVersionId);
// 히트맵 차트 초기화
calendarHeatMap(selectedPdServiceId, selectedVersionId);
// 요구사항 및 연결된 이슈 생성 누적 개수 및 업데이트 상태 현황 멀티 스택바 차트
dailyUpdatedStatusScatterChart(selectedPdServiceId, selectedVersionId);
dailyCreatedCountAndUpdatedStatusesMultiStackCombinationChart(selectedPdServiceId, selectedVersionId);
// timeline chart (비동기 유지 API 2개)
timeLineChart(selectedPdServiceId, selectedVersionId);
$(".ms-parent").css("z-index", 1000);
},
onOpen: function() {
console.log("open event");
$(".ms-parent").css("z-index", 9999);
}
});
}
function dateTimePickerBinding() {
let today = new Date();
$('#scatter_start_date').datetimepicker({
theme:'dark',
onShow: function(ct) {
this.setOptions({
maxDate: $('#scatter_end_date').val()?$('#scatter_end_date').datetimepicker('getValue'):false
})
},
timepicker: false,
format: 'Y-m-d',
onSelectDate: function(ct, $i) {
var startDate = $('#scatter_start_date').datetimepicker('getValue');
var endDate = $('#scatter_end_date').datetimepicker('getValue');
var dayDifference = (endDate - startDate) / (1000 * 60 * 60 * 24);
if (dayDifference > 31) {
alert('시작일과 종료일의 차이는 최대 30일입니다.');
var newDate = new Date(endDate);
newDate.setDate(endDate.getDate() - 30);
$i.val(formatDate(newDate));
}
},
});
$('#scatter_end_date').datetimepicker({
theme:'dark',
onShow: function(ct) {
this.setOptions({
// minDate: $('#scatter_start_date').val()?$('#scatter_start_date').datetimepicker('getValue'):false,
maxDate: today
})
},
timepicker: false,
format: 'Y-m-d',
onSelectDate: function(ct, $i) {
var startDate = $('#scatter_start_date').datetimepicker('getValue');
var endDate = $('#scatter_end_date').datetimepicker('getValue');
var dayDifference = (endDate - startDate) / (1000 * 60 * 60 * 24);
if (dayDifference > 31) {
alert('시작일과 종료일의 차이는 최대 30일입니다.');
var newDate = new Date(startDate);
newDate.setDate(startDate.getDate() + 30);
$i.val(formatDate(newDate));
}
},
// onClose: onScatterChartDateEndChanged
});
$('#multi_stack_start_date').datetimepicker({
theme:'dark',
onShow: function(ct) {
this.setOptions({
maxDate: $('#multi_stack_end_date').val()?$('#multi_stack_end_date').datetimepicker('getValue'):false
})
},
timepicker: false,
format: 'Y-m-d',
onSelectDate: function(ct, $i) {
var startDate = $('#multi_stack_start_date').datetimepicker('getValue');
var endDate = $('#multi_stack_end_date').datetimepicker('getValue');
var dayDifference = (endDate - startDate) / (1000 * 60 * 60 * 24);
if (dayDifference > 31) {
alert('시작일과 종료일의 차이는 최대 30일입니다.');
var newDate = new Date(endDate);
newDate.setDate(endDate.getDate() - 30);
$i.val(formatDate(newDate));
}
}
});
$('#multi_stack_end_date').datetimepicker({
theme:'dark',
onShow: function(ct) {
this.setOptions({
// minDate: $('#multi_stack_start_date').val()?$('#multi_stack_start_date').datetimepicker('getValue'):false,
maxDate: today
})
},
timepicker: false,
format: 'Y-m-d',
onSelectDate: function(ct, $i) {
var startDate = $('#multi_stack_start_date').datetimepicker('getValue');
var endDate = $('#multi_stack_end_date').datetimepicker('getValue');
var dayDifference = (endDate - startDate) / (1000 * 60 * 60 * 24);
if (dayDifference > 31) {
alert('시작일과 종료일의 차이는 최대 30일입니다.');
var newDate = new Date(startDate);
newDate.setDate(startDate.getDate() + 30);
$i.val(formatDate(newDate));
}
},
// onClose: onMultiStackChartDateEndChanged
});
$('#timeline_start_date').datetimepicker({
theme:'dark',
onShow: function(ct) {
this.setOptions({
maxDate: $('#timeline_end_date').val()?$('#timeline_end_date').datetimepicker('getValue'):false
})
},
timepicker: false,
format: 'Y-m-d',
onSelectDate: function(ct, $i) {
var startDate = $('#timeline_start_date').datetimepicker('getValue');
var endDate = $('#timeline_end_date').datetimepicker('getValue');
//var dayDifference = (endDate - startDate) / (1000 * 60 * 60 * 24);
var monthDifference = endDate.getMonth() - startDate.getMonth() +
(12 * (endDate.getFullYear() - startDate.getFullYear()));
if (monthDifference > 6) {
alert('시작일과 종료일의 차이는 최대 6개월입니다.');
var newDate = new Date(endDate);
//newDate.setDate(endDate.getDate() - 30);
newDate.setMonth(endDate.getMonth() - 6);
$i.val(formatDate(newDate));
}
}
});
$('#timeline_end_date').datetimepicker({
theme:'dark',
onShow: function(ct) {
this.setOptions({
// minDate: $('#timeline_start_date').val()?$('#timeline_start_date').datetimepicker('getValue'):false,
maxDate: today
})
},
timepicker: false,
format: 'Y-m-d',
onSelectDate: function(ct, $i) {
var startDate = $('#timeline_start_date').datetimepicker('getValue');
var endDate = $('#timeline_end_date').datetimepicker('getValue');
//var dayDifference = (endDate - startDate) / (1000 * 60 * 60 * 24);
var monthDifference = endDate.getMonth() - startDate.getMonth() +
(12 * (endDate.getFullYear() - startDate.getFullYear()));
if (monthDifference > 6) {
alert('시작일과 종료일의 차이는 최대 6개월입니다.');
var newDate = new Date(startDate);
newDate.setMonth(startDate.getMonth() + 6);
$i.val(formatDate(newDate));
}
},
// onClose: onMultiStackChartDateEndChanged
});
}
function baseDateReset() {
globalDeadline = undefined;
let today = new Date();
$("#scatter_end_date").val(formatDate(today));
$("#multi_stack_end_date").val(formatDate(today));
$("#timeline_end_date").val(formatDate(today));
let aMonthAgo = new Date();
aMonthAgo.setDate(today.getDate() - 30);
$("#scatter_start_date").val(formatDate(aMonthAgo));
$("#multi_stack_start_date").val(formatDate(aMonthAgo));
$("#timeline_start_date").val(formatDate(aMonthAgo));
}
function waitForGlobalDeadline() {
return new Promise(resolve => {
let intervalId = setInterval(() => {
if (globalDeadline !== undefined) {
clearInterval(intervalId);
resolve(globalDeadline);
}
}, 100); // 100ms마다 globalDeadline 값 확인
});
}
function formatDate(date) {
var year = date.getFullYear();
var month = (date.getMonth() + 1).toString().padStart(2, "0");
var day = date.getDate().toString().padStart(2, "0");
return year + "-" + month + "-" + day;
}
function statisticsMonitor(pdserviceId, pdserviceVersionId) {
console.log("[ analysisTime :: statisticsMonitor ] :: 선택된 서비스 ===> " + pdserviceId);
console.log("[ analysisTime :: statisticsMonitor ] :: 선택된 버전 리스트 ===> " + pdserviceVersionId);
$(".spinner").html(
' ' +
"진행 현황 정보를 가져오는 중입니다..."
);
//1. 좌상 게이지 차트 및 타임라인
//2. Time ( 작업일정 ) - 버전 개수 삽입
$.ajax({
url: "/auth-user/api/arms/pdService/getNodeWithVersionOrderByCidDesc?c_id=" + pdserviceId,
type: "GET",
contentType: "application/json;charset=UTF-8",
dataType: "json",
progress: true,
async: false,
statusCode: {
200: function (json) {
let versionData = json.pdServiceVersionEntities;
versionData.sort((a, b) => a.c_id - b.c_id);
let versionCount = versionData.length;
console.log("[ analysisTime :: statisticsMonitor ] :: 등록된 버전 개수 = " + versionCount);
if (versionCount !== undefined) {
$("#version_prgress").text(versionCount);
if (versionCount >= 0) {
let today = new Date();
$("#notifyNoVersion").slideUp();
$("#project-start").show();
$("#project-end").show();
$("#versionGaugeChart").html(""); //게이지 차트 초기화
var versionGauge = [];
var versionTimeline = [];
var versionCustomTimeline = [];
versionData.forEach(function (versionElement, idx) {
if (pdserviceVersionId.includes(versionElement.c_id)) {
var gaugeElement = {
"current_date": today.toString(),
"version_name": versionElement.c_title,
"version_id": versionElement.c_id,
"start_date": (versionElement.c_pds_version_start_date === "start" ? today : versionElement.c_pds_version_start_date),
"end_date": (versionElement.c_pds_version_end_date === "end" ? today : versionElement.c_pds_version_end_date)
}
versionGauge.push(gaugeElement);
}
var timelineElement = {
"id" : versionElement.c_id,
"title" : "버전: "+versionElement.c_title,
"startDate" : (versionElement.c_pds_version_start_date === "start" ? today : versionElement.c_pds_version_start_date),
"endDate" : (versionElement.c_pds_version_end_date === "end" ? today : versionElement.c_pds_version_end_date)
};
versionTimeline.push(timelineElement);
var versionTimelineCustomData = {
"title" : versionElement.c_title,
"startDate" : (versionElement.c_pds_version_start_date === "start" ? today : versionElement.c_pds_version_start_date),
"endDate" : (versionElement.c_pds_version_end_date === "end" ? today : versionElement.c_pds_version_end_date)
};
versionCustomTimeline.push(versionTimelineCustomData);
});
drawVersionProgress(versionGauge); // 버전 게이지
// 이번 달의 첫째 날 구하기
var firstDay = new Date(today.getFullYear(), today.getMonth(), 1);
// 이번 달의 마지막 날 구하기
var lastDay = new Date(today.getFullYear(), today.getMonth() + 1, 0);
// 이번달 일수 구하기
var daysCount = lastDay.getDate();
// 오늘 일자 구하기
var day = today.getDate();
var today_flag = {
title: "오늘",
startDate: formatDate(firstDay),
endDate: formatDate(lastDay),
id: "today_flag"
};
versionTimeline.push(today_flag);
$("#version-timeline-bar").show();
Timeline.init($("#version-timeline-bar"), versionTimeline);
var basePosition = $("#today_flag").css("left");
var baseWidth = $(".month").css("width");
var calFlagPosition = (parseFloat(baseWidth) / daysCount) * day;
var flagPosition = parseFloat(basePosition) + calFlagPosition + "px";
$("#today_flag").removeAttr("style");
$("#today_flag").removeClass("block");
$("#today_flag").css("position", "absolute");
$("#today_flag").css("height", "170px");
$("#today_flag").css("bottom", "-35px");
$("#today_flag span").remove();
$(".block .label").css("text-align", "left");
$("#today_flag").css("left", flagPosition);
$("#today_flag").css("position", "relative");
$("#today_flag").prepend("
오늘
");
$("#today_flag").css("text-align", "center");
// 박스 위치 수정
versionData.forEach(function(version) {
var id = version.c_id;
var start = new Date(version.c_pds_version_start_date);
var startDate = start.getDate();
var daysCount = new Date(start.getFullYear(), start.getMonth() + 1, 0).getDate();
var end = new Date(version.c_pds_version_end_date);
var pos = $("#"+id).css("left");
var baseWidth =(parseFloat($(".month").css("width")))/daysCount;
var diffTime = Math.abs(end - start);
var diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
var realWidth = baseWidth*diffDays
var realPos = parseFloat(pos)+ startDate*baseWidth;
$("#"+id).css("left",realPos+"px");
$("#"+id).css("width",realWidth+"px");
});
window.addEventListener("resize", $("#version-timeline-bar").width);
}
}
}
}
});
}
////////////////////
// 두번째 박스
////////////////////
async function drawVersionProgress(data) {
let gaugeChartColor = ColorPalette.d3Chart.gaugeChart;
if (gaugeChartColor.length < 0) {
gaugeChartColor = [
"rgba(158, 1, 66, 0.8)",
"rgba(213, 62, 79, 0.8)",
"rgba(244, 109, 67, 0.8)",
"rgba(253, 174, 97, 0.8)",
"rgba(254, 224, 139, 0.8)",
"rgba(230, 245, 152, 0.8)",
"rgba(171, 221, 164, 0.8)",
"rgba(102, 194, 165, 0.8)",
"rgba(50, 136, 189, 0.8)",
"rgba(94, 79, 162, 0.8)"
];
}
var Needle,
arc,
arcEndRad,
arcStartRad,
barWidth, // 색션의 두께
chart,
chartInset, // 가운데로 들어간 정도
el,
endPadRad,
height,
i,
margin, // 차트가 그려지는 위치 마진
needle, // 침
numSections, // 색션의 수
padRad,
percToDeg,
percToRad,
degToRad, // 고정
percent,
radius, // 반지름
ref,
sectionIndx, // 색션 인덱스
sectionPerc, // 색션의 퍼센트
startPadRad,
svg,
totalPercent,
width,
versionId,
versionName,
waveName;
// percent = 0.55;
barWidth = 25;
padRad = 0;
chartInset = 11;
totalPercent = 0.75;
margin = {
top: 0,
right: 0,
bottom: 0,
left: 0
};
width = 220;
height = width;
radius = Math.min(width, height) / 2.5;
// percToDeg percToRad degToRad 고정
percToDeg = function (perc) {
return perc * 360;
};
percToRad = function (perc) {
return degToRad(percToDeg(perc));
};
degToRad = function (deg) {
return (deg * Math.PI) / 180;
};
svg = d3
.select("#versionGaugeChart")
.append("svg")
.attr("viewBox", [70, 10, width - 150, height - 100])
.append("g");
chart = svg
.append("g")
.attr("transform", "translate(" + (width + margin.left) / 2 + ", " + (height + margin.top) / 2 + ")");
var tooltip = d3
.select("#versionGaugeChart")
.append("div")
.style("opacity", 0)
.attr("class", "tooltip")
.style("background-color", "white")
.style("border", "solid")
.style("border-width", "1px")
.style("border-radius", "5px")
.style("color", "black")
.style("padding", "10px");
var arc = d3
.arc()
.innerRadius(radius * 0.6)
.outerRadius(radius);
var outerArc = d3
.arc()
.innerRadius(radius * 0.9)
.outerRadius(radius * 0.9);
var totalDate;
numSections = data.length; // 전체 색션의 수(버전의 수)
sectionPerc = 1 / numSections / 2; // '/ 2' for Half-circle
var fastestStartDate;
var latestEndDate;
// 가장 빠른날짜, 가장 느린날짜 세팅
for (var idx = 0; idx < data.length; idx++) {
if (idx === 0) {
fastestStartDate = data[idx].start_date;
latestEndDate = data[idx].end_date;
} else {
if (data[idx].start_date < fastestStartDate) {
fastestStartDate = data[idx].start_date;
}
if (data[idx].end_date > latestEndDate) {
latestEndDate = data[idx].end_date;
}
}
}
globalDeadline = formatDate(new Date(latestEndDate));
console.log("[ analysisTime :: globalDeadline ] :: globalDeadline = " + globalDeadline);
$("#fastestStartDate").text(new Date(fastestStartDate).toLocaleDateString());
$("#latestEndDate").text(new Date(latestEndDate).toLocaleDateString());
const today = new Date(data[0].current_date);
today.setHours(0, 0, 0, 0); //시간, 분, 초, 밀리초를 0으로 설정하여 날짜만 비교
// 시작일과 종료일은 'YYYY-MM-DD' 형식의 문자열로 가정
const startDate = new Date(fastestStartDate);
startDate.setHours(0, 0, 0, 0); //시간, 분, 초, 밀리초를 0으로 설정하여 날짜만 비교
const endDate = new Date(latestEndDate);
endDate.setHours(0, 0, 0, 0); //시간, 분, 초, 밀리초를 0으로 설정하여 날짜만 비교
var diffStart = (today - startDate) / (1000 * 60 * 60 * 24); // 오늘 날짜와 시작일 사이의 차이를 일 단위로 계산
var diffEnd = (today - endDate) / (1000 * 60 * 60 * 24); // 오늘 날짜와 종료일 사이의 차이를 일 단위로 계산
$("#startDDay").css("color", "");
$("#endDDay").css("color", "");
if (diffStart > 0) {
$("#startDDay").text("D + " + diffStart);
} else if (diffStart === 0) {
$("#startDDay").text("D - day");
} else {
diffStart *= -1;
$("#startDDay").text("D - " + diffStart);
}
if (diffEnd > 0) {
$("#endDDay")
.css("color", "#FF4D4D")
.css("font-weight", "bold")
.text("D + " + diffEnd)
.append(" 초과");
} else if (diffEnd === 0) {
$("#endDDay").text("D - day");
} else {
diffEnd *= -1;
$("#endDDay").text("D - " + diffEnd);
}
totalDate = Math.floor(Math.abs((new Date(latestEndDate) - new Date(fastestStartDate)) / (1000 * 60 * 60 * 24)) + 1);
var mouseover = function (d) {
var hoverData = d;
var subgroupId = hoverData.version_id;
var subgroupName = hoverData.version_name;
var subgroupValue = new Date(hoverData.start_date).toLocaleDateString() + " ~ " + new Date(hoverData.end_date).toLocaleDateString();
tooltip.html("버전명: " + subgroupName + "
" + "기간: " + subgroupValue).style("opacity", 1);
d3.selectAll(".myWave").style("opacity", 0.2);
d3.selectAll(".myStr").style("opacity", 0.2);
d3.selectAll(".wave-" + subgroupId).style("opacity", 1);
};
var mousemove = function (d) {
var [x, y] = d3.mouse(this);
tooltip.style("left", (x + 120) + "px").style("top", (y + 150) + "px");
};
var mouseleave = function (d) {
tooltip.style("opacity", 0);
d3.selectAll(".myStr").style("opacity", 1);
d3.selectAll(".myWave").style("opacity", 1);
};
for (sectionIndx = i = 1, ref = numSections; 1 <= ref ? i <= ref : i >= ref; sectionIndx = 1 <= ref ? ++i : --i) {
arcStartRad = percToRad(totalPercent);
arcEndRad = arcStartRad + percToRad(sectionPerc);
totalPercent += sectionPerc;
startPadRad = sectionIndx === 0 ? 0 : padRad / 2;
endPadRad = sectionIndx === numSections ? 0 : padRad / 2;
versionId = data[sectionIndx - 1].version_id;
versionName = data[sectionIndx - 1].version_name;
var sectionData = data[sectionIndx - 1];
var arc = d3
.arc()
.outerRadius(radius - chartInset)
.innerRadius(radius - chartInset - barWidth)
.startAngle(arcStartRad + startPadRad)
.endAngle(arcEndRad - endPadRad);
var section = chart.selectAll(".arc.chart-color" + sectionIndx + ".myWave.wave-" + versionId);
section
.data([sectionData])
.enter()
.append("g")
.attr("class", "arc chart-color" + sectionIndx + " myWave wave-" + versionId)
.on("mouseover", mouseover)
.on("mousemove", mousemove)
.on("mouseleave", mouseleave)
.append("path")
.attr("fill", function (d) {
return gaugeChartColor[(sectionIndx - 1) % gaugeChartColor.length];
})
.attr("stroke", "white")
.style("stroke-width", "0.4px")
.attr("d", arc);
chart
.selectAll(".arc.chart-color" + sectionIndx + ".myWave.wave-" + versionId)
.append("text")
.attr("class", "no-select")
.text(function (d) {
return getStrLimit(d.version_name, 9);
})
.attr("x", function (d) {
return arc.centroid(d)[0];
})
.attr("y", function (d) {
return arc.centroid(d)[1] + 2;
})
.style("font-size", "10px")
.style("font-weight", "700")
.attr("text-anchor", "middle");
}
Needle = (function () {
function Needle(len, radius1) {
this.len = len;
this.radius = radius1;
}
Needle.prototype.drawOn = function (el, perc) {
el.append("circle")
.attr("class", "needle-center")
.attr("cx", 0)
.attr("cy", -10)
.attr("r", this.radius)
.attr("stroke", "white")
.style("stroke-width", "0.3px");
return el
.append("path")
.attr("class", "needle")
.attr("d", this.mkCmd(perc))
.attr("stroke", "white")
.style("stroke-width", "0.3px");
};
Needle.prototype.animateOn = function (el, perc) {
var self;
self = this;
return el
.selectAll(".needle")
.transition()
.delay(500)
.ease(d3.easeElasticOut)
.duration(3000)
.attrTween("progress", function () {
return function (percentOfPercent) {
var progress;
progress = percentOfPercent * perc;
return d3.select(".needle").attr("d", self.mkCmd(progress));
};
});
};
Needle.prototype.mkCmd = function (perc) {
var centerX, centerY, leftX, leftY, rightX, rightY, thetaRad, topX, topY;
thetaRad = percToRad(perc / 2);
centerX = 0;
centerY = -10;
topX = centerX - this.len * Math.cos(thetaRad);
topY = centerY - this.len * Math.sin(thetaRad);
leftX = centerX - this.radius * Math.cos(thetaRad - Math.PI / 2);
leftY = centerY - this.radius * Math.sin(thetaRad - Math.PI / 2);
rightX = centerX - this.radius * Math.cos(thetaRad + Math.PI / 2);
rightY = centerY - this.radius * Math.sin(thetaRad + Math.PI / 2);
return "M " + leftX + " " + leftY + " L " + topX + " " + topY + " L " + rightX + " " + rightY;
};
return Needle;
})();
needle = new Needle(35, 3);
needle.drawOn(chart, 0);
var needleAngle = (diffStart + 1) / totalDate;
if (needleAngle > 1) {
needleAngle = 1;
} else if (needleAngle < 0) {
needleAngle = 0;
}
needle.animateOn(chart, needleAngle);
}
////////////////////
// 히트맵 차트
////////////////////
function calendarHeatMap(pdServiceLink, pdServiceVersions) {
$("#calendar_yearview_blocks_chart_1 svg").remove();
$("#calendar_yearview_blocks_chart_2 svg").remove();
$.ajax({
url: "/auth-admin/api/arms/analysis/time/heatmap",
type: "GET",
data: { pdServiceLink: pdServiceLink, pdServiceVersionLinks: pdServiceVersions },
contentType: "application/json;charset=UTF-8",
dataType: "json",
progress: true,
async: false,
statusCode: {
200: function (data) {
console.log("[ analysisTime :: calendarHeatMap ] :: 누적 업데이트 히트맵 차트데이터 = ");
console.log(data);
$(".update-title").show();
$("#calendar_yearview_blocks_chart_1").calendar_yearview_blocks({
data: JSON.stringify(data.requirement),
start_monday: true,
always_show_tooltip: true,
month_names: ["jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sept", "oct", "nov", "dec"],
day_names: ["mon", "wed", "fri", "sun"]
});
$("#calendar_yearview_blocks_chart_2").calendar_yearview_blocks({
data: JSON.stringify(data.relationIssue),
start_monday: true,
always_show_tooltip: true,
month_names: ["jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec"],
day_names: ["mon", "wed", "fri", "sun"]
});
}
}
});
}
////////////////////
// 스캐터 차트
////////////////////
async function dailyUpdatedStatusScatterChart(pdServiceLink, pdServiceVersionLinks) {
let deadline = await waitForGlobalDeadline();
let startDate = $("#scatter_start_date").val();
let endDate = $("#scatter_end_date").val();
if (!validateSearchDateWithChart(startDate, endDate)) {
return;
}
const url = new UrlBuilder()
.setBaseUrl("/auth-admin/api/arms/analysis/time/alm-issue/aggregation/by-date")
.addQueryParam("pdServiceLink", pdServiceLink)
.addQueryParam("pdServiceVersionLinks", pdServiceVersionLinks)
.addQueryParam("기준일_필드명", "updated")
.addQueryParam("시작일", startDate)
.addQueryParam("종료일", endDate)
.addQueryParam("컨텐츠_보기_여부", true)
.build();
$(".spinner").html(
' ' +
"일별 업데이트 상태 차트를 로딩 중입니다..."
);
$.ajax({
url: url,
type: "GET",
contentType: "application/json;charset=UTF-8",
dataType: "json",
progress: true,
async: false,
statusCode: {
200: function (data) {
console.log("[ analysisTime :: dailyUpdatedStatusScatterChart ] :: 일별 업데이트 상태 스캐터 차트데이터 = ");
console.log(data);
let result = Object.keys(data).reduce(
(acc, date) => {
if (data[date].totalRequirements !== 0 || data[date].totalRelationIssues !== 0) {
acc.dates.push(date);
acc.totalRequirements.push(data[date].totalRequirements);
acc.totalRelationIssues.push(data[date].totalRelationIssues);
}
return acc;
},
{
dates: [],
totalRelationIssues: [],
totalRequirements: [],
}
);
let dates = result.dates;
let totalRelationIssues = result.totalRelationIssues;
let totalRequirements = result.totalRequirements;
let deadlineSeries = createDeadlineSeries(dates, totalRequirements, totalRelationIssues, globalDeadline, false, 2);
var dom = document.getElementById("scatter-chart-container");
var myChart = echarts.init(dom, "dark", {
renderer: "canvas",
useDirtyRect: false
});
var option;
if ((totalRequirements && totalRequirements.length > 0) || (totalRelationIssues && totalRelationIssues.length > 0)) {
option = {
aria: {
show: true
},
legend: {
data: ["요구사항", "연결된 이슈"],
textStyle: {
color: "white"
}
},
xAxis: {
type: "category",
axisTick: { show: false },
data: dates,
axisLabel: {
textStyle: {
color: "white"
}
}
},
yAxis: {
type: "value",
splitLine: {
show: true,
lineStyle: {
color: "rgba(255,255,255,0.2)",
width: 1,
type: "dashed"
}
},
axisLabel: {
textStyle: {
color: "white"
}
}
},
series: [
{
name: "요구사항",
data: totalRequirements,
type: "scatter",
symbol: "diamond",
clip: false,
label: {
normal: {
show: false,
color: "#FFFFFF"
},
emphasis: {
show: true,
color: "#FFFFFF"
}
},
symbolSize: function (val) {
var sbSize = 10;
if (val > 10) {
sbSize = val * 1.1;
} else if (val === 0) {
sbSize = 0;
}
return sbSize;
}
},
{
name: "연결된 이슈",
data: totalRelationIssues,
type: "scatter",
clip: false,
label: {
normal: {
show: false,
color: "#FFFFFF"
},
emphasis: {
show: true,
color: "#FFFFFF"
}
},
symbolSize: function (val) {
var sbSize = 10;
if (val > 10) {
sbSize = val * 1.1;
} else if (val === 0) {
sbSize = 0;
}
return sbSize;
},
itemStyle: {
color: "#13de57"
}
},
...deadlineSeries
],
tooltip: {
trigger: "axis",
position: "top",
borderWidth: 1,
axisPointer: {
type: "line",
label: {
formatter: function (params) {
return formatDate(new Date(params.value));
}
}
}
},
backgroundColor: "rgba(255,255,255,0)",
animationDelay: function (idx) {
return idx * 20;
},
animationDelayUpdate: function (idx) {
return idx * 20;
}
};
myChart.on("click", function (params) {
// console.log(params.data);
});
myChart.on("mouseover", function (params) {
// if (params.seriesType === 'line') {
var option = myChart.getOption();
option.series[params.seriesIndex].label.color = 'white';
option.series[params.seriesIndex].label.show = true;
myChart.setOption(option);
// }
});
myChart.on("mouseout", function (params) {
// if (params.seriesType === 'line') {
var option = myChart.getOption();
option.series[params.seriesIndex].label.show = false;
myChart.setOption(option);
// }
});
}
else {
option = {
title: {
text: "데이터가 없습니다",
left: "center",
top: "middle",
textStyle: {
color: "#fff"
}
},
backgroundColor: "rgba(255,255,255,0)"
};
}
if (option && typeof option === "object") {
myChart.setOption(option, true);
}
window.addEventListener("resize", myChart.resize);
}
}
});
}
////////////////
// 멀티 콤비네이션 차트
///////////////
async function dailyCreatedCountAndUpdatedStatusesMultiStackCombinationChart(pdServiceLink, pdServiceVersionLinks) {
let deadline = await waitForGlobalDeadline();
let startDate = $("#multi_stack_start_date").val();
let endDate = $("#multi_stack_end_date").val();
if (!validateSearchDateWithChart(startDate, endDate)) {
return;
}
const url = new UrlBuilder()
.setBaseUrl("/auth-admin/api/arms/analysis/time/issue-status/aggregation/by-date")
.addQueryParam("pdServiceLink", pdServiceLink)
.addQueryParam("pdServiceVersionLinks", pdServiceVersionLinks)
.addQueryParam("기준일_필드명", "updated")
.addQueryParam("시작일", startDate)
.addQueryParam("종료일", endDate)
.build();
$(".spinner").html(
' ' +
"생성 개수 및 업데이트 상태 현황 정보를 로딩 중입니다..."
);
$.ajax({
url: url,
type: "GET",
contentType: "application/json;charset=UTF-8",
dataType: "json",
progress: true,
async: false,
statusCode: {
200: function (data) {
console.log("[ analysisTime :: dailyCreatedCountAndUpdatedStatusesMultiStackCombinationChart ] :: 일별 이슈 생성 개수 및 업데이트 현황 데이터 = ");
console.log(data);
var accumulateRequirementCount = 0;
var accumulateRelationIssueCount = 0;
let result = Object.keys(data).reduce(
(acc, date) => {
if (data[date].totalRequirements !== 0 || data[date].totalRelationIssues !== 0) {
acc.dates.push(date);
accumulateRequirementCount += data[date].totalRequirements;
accumulateRelationIssueCount += data[date].totalRelationIssues;
acc.totalRequirements.push(accumulateRequirementCount);
acc.totalRelationIssues.push(accumulateRelationIssueCount);
}
if (data[date].requirementStatuses) {
console.log(data[date].requirementStatuses)
Object.keys(data[date].requirementStatuses).forEach((status) => {
if (!acc.statusKeys.includes(status)) {
acc.statusKeys.push(status);
}
});
}
if (data[date].relationIssueStatuses) {
Object.keys(data[date].relationIssueStatuses).forEach((status) => {
if (!acc.statusKeys.includes(status)) {
acc.statusKeys.push(status);
}
});
}
return acc;
},
{
dates: [],
totalRelationIssues: [],
totalRequirements: [],
statusKeys: []
}
);
var dom = document.getElementById("multi-chart-container");
var myChart = echarts.init(dom, null, {
renderer: "canvas",
useDirtyRect: false
});
var option;
if (result.dates.length > 0) {
var labelOption = {
show: false,
position: "top",
distance: 0,
align: "center",
verticalAlign: "top",
rotate: 0,
formatter: "{c}",
fontSize: 14,
rich: {
name: {}
}
};
let dates = result.dates;
let totalRelationIssues = result.totalRelationIssues;
let totalRequirements = result.totalRequirements;
let statusKeys = result.statusKeys;
let deadlineSeries = createDeadlineSeries(dates, totalRequirements, totalRelationIssues, globalDeadline, true, 4);
let requirementStatusSeries = statusKeys.map((key, i) => {
let stackType = "요구사항";
return {
name: key,
type: "bar",
stack: stackType,
label: labelOption,
emphasis: {
focus: "series"
},
data: dates.map((date) => {
if (data[date] && data[date].requirementStatuses && Object.keys(data[date].requirementStatuses).length > 0) {
return { value: data[date].requirementStatuses[key] || 0, stackType: stackType };
} else {
return { value: 0, stackType: stackType };
}
})
};
});
let relationIssueStatusSeries = statusKeys.map((key, i) => {
let stackType = "연결된 이슈";
return {
name: key,
type: "bar",
stack: stackType,
label: labelOption,
emphasis: {
focus: "series"
},
data: dates.map((date) => {
if (data[date] && data[date].relationIssueStatuses && Object.keys(data[date].relationIssueStatuses).length > 0) {
return { value: data[date].relationIssueStatuses[key] || 0, stackType: stackType };
} else {
return { value: 0, stackType: stackType };
}
})
};
});
// let stackIndex = statusKeys.map((value, index) => index);
statusKeys.push("요구사항");
statusKeys.push("연결된 이슈");
let multiCombinationChartSeries = [
...requirementStatusSeries,
...relationIssueStatusSeries,
{
name: "요구사항",
type: "line",
// yAxisIndex: 1,
emphasis: {
focus: "series"
},
symbolSize: 10,
data: totalRequirements
},
{
name: "연결된 이슈",
type: "line",
// yAxisIndex: 1,
emphasis: {
focus: "series"
},
symbolSize: 10,
data: totalRelationIssues
},
...deadlineSeries
];
var legendData = statusKeys;
var xAiasData = dates;
option = {
tooltip: {
trigger: "axis",
axisPointer: {
type: "shadow"
},
formatter: function(params) {
var tooltipText = "";
tooltipText += params[0].axisValue + "
";
params.forEach(function(item) {
if (item.value !== 0) {
// 0인 데이터는 무시
if (item.seriesType === "bar") {
var stackType = item.data.stackType; // 추가 정보에 접근
tooltipText +=
item.marker +
item.seriesName +
"[" +
stackType +
"]" +
' ' +
item.value +
"" +
"
";
} else if (item.seriesType === "line") {
tooltipText +=
item.marker +
item.seriesName +
' ' +
item.value +
"" +
"
";
}
}
});
return tooltipText;
}
},
legend: {
data: legendData,
textStyle: {
color: "white"
},
tooltip: {
show: true
}
},
grid: {
top: "20%",
containLabel: false
},
toolbox: {
show: true,
orient: "vertical",
left: "right",
bottom: "50px",
feature: {
mark: { show: true },
// dataView: {show: true, readOnly: true},
/*magicType: {
show: true,
type: ['stack'],
seriesIndex: {
stack: stackIndex
}
},*/
dataZoom: {
show: true
}
// restore: { show: true },
//saveAsImage: { show: true }
// myTool: {
// show: true,
// title: '상태 그룹화',
// icon: 'image://http://echarts.baidu.com/images/favicon.png',
// onclick: toggleStack
// },
},
iconStyle: {
borderColor: "white"
}
},
xAxis: [
{
type: "category",
axisTick: { show: false },
data: xAiasData,
axisLabel: {
textStyle: {
color: "white"
}
}
}
],
yAxis: [
{
type: "value",
axisLabel: {
textStyle: {
color: "white"
}
},
splitLine: {
show: true,
lineStyle: {
color: "rgba(255,255,255,0.2)",
width: 1,
type: "dashed"
}
}
},
{
type: "value",
position: "right",
axisLabel: {
textStyle: {
color: "white"
}
}
}
],
series: multiCombinationChartSeries,
backgroundColor: "rgba(255,255,255,0)",
animationDelay: function(idx) {
return idx * 20;
},
animationDelayUpdate: function(idx) {
return idx * 20;
}
};
} else {
option = {
title: {
text: "데이터가 없습니다",
left: "center",
top: "middle",
textStyle: {
color: "#fff" // 제목 색상을 검은색으로 변경
}
},
backgroundColor: "rgba(255,255,255,0)"
};
}
if (option && typeof option === "object") {
myChart.setOption(option, true);
}
window.addEventListener("resize", function() {
myChart.resize();
});
myChart.on("mouseover", function(params) {
var option = myChart.getOption();
option.series[params.seriesIndex].label.show = true;
myChart.setOption(option);
});
myChart.on("mouseout", function(params) {
var option = myChart.getOption();
option.series[params.seriesIndex].label.show = false;
myChart.setOption(option);
});
}
}
});
}
// 마감일 함수
function createDeadlineSeries(dates, totalRelationIssues, totalRequirements, deadline, usePreviousValue, lineWidth) {
var chartStart = dates.reduce((earliest, date) => (date < earliest ? date : earliest), dates[0]);
var chartEnd = dates.reduce((latest, date) => (date > latest ? date : latest), dates[0]);
chartStart = new Date(chartStart);
chartEnd = new Date(chartEnd);
var deadlineSeries = [];
if (new Date(deadline) <= chartEnd) {
if (!dates.includes(deadline)) {
dates.push(deadline);
dates.sort((a, b) => new Date(a) - new Date(b));
let dateIndex = dates.indexOf(deadline);
if (dateIndex > 0 && usePreviousValue) {
totalRequirements.splice(dateIndex, 0, totalRequirements[dateIndex-1]);
totalRelationIssues.splice(dateIndex, 0, totalRelationIssues[dateIndex-1]);
}
else {
totalRequirements.splice(dateIndex, 0, 0);
totalRelationIssues.splice(dateIndex, 0, 0);
}
}
// 데이터 추가
var vs = {
name: "마감일",
type: "line",
data: [
[deadline, 0],
[deadline, 1]
],
tooltip: {
show: false
},
markLine: {
silent: true,
symbol: "none",
data: [
{
xAxis: deadline
}
],
lineStyle: {
color: "red",
width: lineWidth,
type: "dashed"
},
label: {
formatter: "마감일 : {c}",
color: "white",
fontSize: 14,
fontWeight: "bold"
}
},
lineStyle: {
color: "red",
type: "dashed"
},
symbol: "none"
};
deadlineSeries.push(vs);
}
return deadlineSeries;
}
function dailyChartDataSearchEvent() {
$("#scatter-search").on("click", function (params) {
dailyUpdatedStatusScatterChart(selectedPdServiceId, selectedVersionId);
});
$("#multi-stack-search").on("click", function (params) {
dailyCreatedCountAndUpdatedStatusesMultiStackCombinationChart(selectedPdServiceId, selectedVersionId);
});
$("#timeline-search").on("click", function (params) {
timeLineChart(selectedPdServiceId, selectedVersionId);
});
}
function validateSearchDateWithChart(startDate, endDate) {
let result = true;
if(!selectedPdServiceId || !selectedVersionId) {
alert("제품(서비스) 혹은 버전 선택이 되지 않았습니다.");
result = false;
}
if (!startDate || !endDate) {
alert("일자를 지정하지 않았습니다.");
result = false;
}
return result;
}
function onScatterChartDateEndChanged() {
dailyUpdatedStatusScatterChart(selectedPdServiceId, selectedVersionId);
}
function onMultiStackChartDateEndChanged() {
dailyCreatedCountAndUpdatedStatusesMultiStackCombinationChart(selectedPdServiceId, selectedVersionId);
}
function convertVersionIdToTitle(versionId) {
if (versionListData.hasOwnProperty(versionId)) {
var version = versionListData[versionId];
return version.c_title;
}
}
function verticalTimeLineChart(data) {
let contentSet = {}; // 객체로 선언
let items = Object.values(data).reduce((acc, versionData) => {
versionData.forEach(item => {
if (!contentSet[item.summary]) { // 중복 체크
contentSet[item.summary] = {
version: item.pdServiceVersion,
summary: item.summary,
projectName: [item.project.project_name],
date: formatDateTime(item.updated)
};
} else {
// projectName에 item.project.project_name이 없는 경우에만 추가
if (!contentSet[item.summary].projectName.includes(item.project.project_name)) {
contentSet[item.summary].projectName.push(item.project.project_name);
contentSet[item.summary].projectName.sort();
}
}
});
return acc;
}, []);
items = Object.values(contentSet).map(item => ({
...item,
projectName: item.projectName
}));
// 날짜를 기준으로 오름차순 정렬
items.sort((a, b) => new Date(b.date) - new Date(a.date));
makeVerticalTimeline(items);
// mock data
/*
makeVerticalTimeline([
{
title: "BaseVersion",
content: "요구 사항 이슈 1",
type: "Presentation",
date: "2023-11-08"
},
{
title: "",
content: "요구 사항 이슈 2",
type: "Presentation",
date: "2023-11-29"
},
{
title: "1.0",
content: "요구 사항 이슈 3",
type: "Review",
date: "2023-12-01"
},
{
title: "1.1",
content: "요구 사항 이슈 4",
type: "Review",
date: "2023-12-11"
}
]);
*/
}
function makeVerticalTimeline(data) {
// 데이터 세팅
const $container = $(".timeline-container");
$container.empty();
if (data.length == 0) {
const noDataMessage = $('').text('데이터가 없습니다.').css({
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)'
});
$container.append(noDataMessage);
} else {
// 날짜별로 데이터 그룹화
let groupedData = data.reduce((group, item) => {
let date = item.date;
if (!group[date]) group[date] = [];
group[date].push(item);
return group;
}, {});
const $ul = $('');
Object.entries(groupedData).forEach(([date, items]) => {
items.forEach(({ version, summary, projectName }, index) => {
const $li = $('').addClass('session');
if (index === 0) {
$li.append(`
${date}
`);
}
const $sessionContent = $(`
${convertVersionIdToTitle(version)}
${summary}
`);
const $projectNameDiv = $('').addClass('project-names');
// projectName 배열의 각 요소를 추가
projectName.forEach(name => {
const $button = $('').addClass('project-name').text(name);
$projectNameDiv.append($button);
});
$sessionContent.append($projectNameDiv);
$li.append($sessionContent);
$ul.append($li);
});
});
$container.append($ul);
}
adjustHeight();
}
function formatDateTime(dateTime) {
var date = dateTime.split('T')[0];
return date;
}
async function timeLineChart(pdServiceLink, pdServiceVersionLinks) {
let deadline = await waitForGlobalDeadline();
let startDate = $("#timeline_start_date").val();
let endDate = $("#timeline_end_date").val();
if (!validateSearchDateWithChart(startDate, endDate)) {
return;
}
const verticalUrl = new UrlBuilder()
.setBaseUrl("/auth-admin/api/arms/analysis/time/standard-daily/updated-jira-issue")
.addQueryParam("pdServiceLink", pdServiceLink)
.addQueryParam("pdServiceVersionLinks", pdServiceVersionLinks)
.addQueryParam("기준일_필드명", "updated")
.addQueryParam("isReqType", "REQUIREMENT")
.addQueryParam("시작일", startDate)
.addQueryParam("종료일", endDate)
.addQueryParam("크기", 1000)
.addQueryParam("하위_크기", 1000)
.addQueryParam("컨텐츠_보기_여부", true)
.build();
$(".spinner").html(
' ' +
"수직 타임라인 차트를 로딩 중입니다..."
);
$.ajax({
url: verticalUrl,
type: "GET",
contentType: "application/json;charset=UTF-8",
dataType: "json",
progress: true,
statusCode: {
200: function (data) {
console.log("[ analysisTime :: TimeLineData ] :: = ");
console.log(data);
verticalTimeLineChart(data);
}
}
});
const ridgeLineUrl = new UrlBuilder()
.setBaseUrl("/auth-admin/api/arms/analysis/time/standard-daily/updated-ridgeline")
.addQueryParam("pdServiceLink", pdServiceLink)
.addQueryParam("pdServiceVersionLinks", pdServiceVersionLinks)
.addQueryParam("기준일_필드명", "updated")
.addQueryParam("isReqType", "ISSUE")
.addQueryParam("시작일", startDate)
.addQueryParam("종료일", endDate)
.addQueryParam("크기", 1000)
.addQueryParam("하위_크기", 1000)
.addQueryParam("컨텐츠_보기_여부", true)
.build();
function executeAjaxCall(url) {
$(".spinner").html(
' ' +
"요구사항 업데이트 현황(능선차트)를 로딩 중입니다..."
);
$.ajax({
url: url,
type: "GET",
contentType: "application/json;charset=UTF-8",
dataType: "json",
progress: true,
statusCode: {
200: function (data) {
console.log("[ analysisTime :: ridgeLineData ] :: = ");
console.log(data);
updateRidgeLine(data);
}
}
});
}
executeAjaxCall(ridgeLineUrl);
window.addEventListener("resize", function() {
adjustHeight();
});
}
function getColorByVersion(version) {
var colorPalette = [ //e chart 컬러 팔레트
'#c23531','#2f4554','#61a0a8','#d48265','#91c7ae',
'#749f83','#ca8622','#bda29a','#6e7074','#546570',
'#c4ccd3'
];
var versionNumber = parseInt(version);
return colorPalette[versionNumber % colorPalette.length];
}
function updateRidgeLine(traffic){
// 데이터가 없을 경우
if (!traffic || traffic.length === 0) {
document.getElementById("overlapInputDiv").style.display = "none";
document.getElementById("updateRidgeLine").innerHTML = "" +
"데이터가 없습니다.
";
return;
} else {
document.getElementById("overlapInputDiv").style.display = "flex";
}
function setOverlapInputListener() {
var overlap = this.value;
overlapNumberInput.value = overlap;
drawGraph(traffic, overlap);
}
function setOverlapNumberInputListener() {
var overlap = this.value;
overlapInput.value = overlap;
drawGraph(traffic, overlap);
}
overlapInput.removeEventListener('input', setOverlapInputListener);
overlapNumberInput.removeEventListener('input', setOverlapNumberInputListener);
overlapInput.addEventListener('input', setOverlapInputListener);
overlapNumberInput.addEventListener('input', setOverlapNumberInputListener);
var initialOverlap = traffic.length > 30 ? 5 : 2;
document.getElementById("overlapInput").value = initialOverlap;
document.getElementById("overlapNumberInput").value = initialOverlap;
drawGraph(traffic, initialOverlap);
}
function drawGraph(traffic, overlap){
document.getElementById("updateRidgeLine").innerHTML = "";
var nestedDataByDate = d3.nest()
.key(function(d) { return +new Date(d.date); })
.entries(traffic);
var dates = nestedDataByDate.map(function(d) { return +d.key; }).sort(d3.ascending);
var nestedDataByName = d3.nest()
.key(function(d) { return d.name; })
.entries(traffic);
var series = nestedDataByName.map(function(d) {
var valuesMap = d3.map(d.values, function(e) { return String(+new Date(e.date)); });
var values = dates.map(function(date) {
var valueObj = valuesMap.get(String(date));
return valueObj ? valueObj.value : null;
});
var version = d.values[0] ? d.values[0].version : null; // version 필드 추가
var summary = d.values[0] ? d.values[0].summary : null; // version 필드 추가
var key = d.values[0] ? d.values[0].name : null; // version 필드 추가
return { name: key+": "+summary, values: values, version: version ,key:key}; // version 값 포함하여 반환
});
//const overlap = 4;
const width = 900;
//const height = series.length * 30;
const minHeight = 600;
const maxHeight = 650;
const height = Math.max(minHeight, Math.min(maxHeight, series.length * 16));
const marginTop = 50;
const marginRight = 0;
const marginBottom = 0;
const marginLeft = 280;
// Create the scales.
const x = d3.scaleTime()
.domain(d3.extent(dates))
.range([marginLeft, width - marginRight]);
const y = d3.scalePoint()
.domain(series.map(d => d.name))
.range([marginTop, height - marginBottom]);
const z = d3.scaleLinear()
.domain([0, d3.max(series, d => d3.max(d.values))]).nice()
.range([0, -overlap * y.step()]);
// Create the area generator and its top-line generator.
const area = d3.area()
.curve(d3.curveBasis)
.defined(d => !isNaN(d))
.x((d, i) => x(dates[i]))
.y0(0)
.y1(d => z(d));
const line = area.lineY1();
// Create the SVG container.
const svg = d3.create("svg")
.attr("width", width)
.attr("height", maxHeight)
.attr("viewBox", [0, 0, width, height])
.attr("style", "max-width: 100%; height: auto;");
// Append the axes.
svg.append("g")
.attr("transform", `translate(0,${height - marginBottom})`)
.call(d3.axisBottom(x)
.ticks(width / 80)
.tickSizeOuter(0));
svg.append("g")
.attr("transform", `translate(${marginLeft},0)`)
.call(d3.axisLeft(y).tickSize(0).tickPadding(4))
.call(g => g.select(".domain").remove())
.selectAll(".tick text")
.text(function(d) {
return d.length > 42 ? d.slice(0, 35) + ' . . .' : d; // 긴 레이블은 축약
})
.style("font-size", "10px");
// Append a layer for each series.
const group = svg.append("g")
.selectAll("g")
.data(series)
.join("g")
.attr("transform", d => `translate(0,${y(d.name) + 1})`);
var div = d3.select("body").append("div")
.attr("class", "tooltip")
.style("opacity", 0);
group.append("path")
.attr("fill", d => getColorByVersion(d.version))
.attr("d", d => area(d.values))
.on("mouseover", function(d) {
var event = d3.event;
d3.select(this)
.transition()
.duration(20)
.style("opacity", 0.4);
div.transition()
.duration(20)
.style("opacity", .9);
div.html("버전 정보: " + convertVersionIdToTitle(d.version) +
"
요구사항 키: " + d.key +
"
요구사항 제목: " + d.name)
.style("left", (event.pageX) + "px")
.style("top", (event.pageY - 28) + "px");
})
.on("mouseout", function() {
d3.select(this)
.transition()
.duration(20)
.style("opacity", 1);
div.transition()
.duration(20)
.style("opacity", 0);
});
group.append("path")
.attr("fill", "none")
.attr("stroke","#EBEDF0") //.attr("stroke", d => getColorByVersion(d.version))
.attr("stroke-width", 0.5)
.attr("d", d => line(d.values));
$("#overlapInputDiv").css("display", "flex");
$('#updateRidgeLine').append(svg.node());
adjustHeight();
}
// 차트 높이 조정
function adjustHeight() {
var verticalTimeline = $('#vertical-timeline');
var updateRidgeLine = $('#updateRidgeLine');
if (verticalTimeline && updateRidgeLine) {
verticalTimeline.height(updateRidgeLine.height() + 20);
}
}