///////////////////
//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",
// jspreadsheet
"../reference/jquery-plugins/jspreadsheet-ce-4.13.1/dist/jsuites.js",
"../reference/jquery-plugins/jspreadsheet-ce-4.13.1/dist/jsuites.css",
"../reference/jquery-plugins/jspreadsheet-ce-4.13.1/dist/index.js",
"../reference/jquery-plugins/jspreadsheet-ce-4.13.1/dist/jspreadsheet.css",
"../reference/jquery-plugins/jspreadsheet-ce-4.13.1/dist/jspreadsheet.theme.css"
],
[
// echarts
"../reference/jquery-plugins/echarts-5.5.0/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_insight", "sidebar_menu_analysis", "sidebar_menu_analysis_time");
//제품(서비스) 셀렉트 박스 이니시에이터
makePdServiceSelectBox();
//버전 멀티 셀렉트 박스 이니시에이터
makeVersionMultiSelectBox();
// 높이 조정
$(".top-menu-div").matchHeight({
target: $(".top-menu-div-scope")
});
})
.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 === "" ) {
return;
}
baseDateReset();
// 최상단 메뉴 세팅
TopMenuApi.톱메뉴_초기화();
TopMenuApi.톱메뉴_세팅();
// 버전 및 게이지차트, 버전 타임라인 차트 초기화
statisticsMonitor(selectedPdServiceId, selectedVersionId);
// 히트맵 차트 초기화
calendarHeatMap(selectedPdServiceId, selectedVersionId);
// 요구사항 및 연결된 이슈 생성 누적 개수 및 업데이트 상태 현황 멀티 스택바 차트
dailyUpdatedStatusScatterChart(selectedPdServiceId, selectedVersionId);
dailyCreatedCountAndUpdatedStatusesMultiStackCombinationChart(selectedPdServiceId, selectedVersionId);
// Vertical Time Line
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 == "") {
jError("버전이 선택되지 않았습니다.");
$(".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() {
// Scatter Chart
initializeScatterChart();
// Multi Stack Chart
initializeMultiStackChart();
// Timeline Chart
initializeTimelineChart();
}
function bindStartDatePicker(startSelector, endSelector, maxRangeDays) {
$(startSelector).datetimepicker({
theme: "dark",
lang: "kr",
onShow: function (ct) {
this.setOptions({
maxDate: $(endSelector).val() ? $(endSelector).datetimepicker("getValue") : false
});
},
timepicker: false,
format: "Y-m-d",
onSelectDate: function (ct, $i) {
var startDate = $(startSelector).datetimepicker("getValue");
var endDate = $(endSelector).datetimepicker("getValue");
if (!endDate) {
var newEndDate = new Date(startDate);
newEndDate.setDate(startDate.getDate() + maxRangeDays);
$(endSelector).val(formatDate(newEndDate));
} else {
var dayDifference = (endDate - startDate) / (1000 * 60 * 60 * 24);
if (dayDifference > maxRangeDays) {
var newEndDate = new Date(startDate);
newEndDate.setDate(startDate.getDate() + maxRangeDays);
$(endSelector).val(formatDate(newEndDate));
}
}
}
});
}
function bindEndDatePicker(startSelector, endSelector, maxRangeDays) {
$(endSelector).datetimepicker({
theme: "dark",
lang: "kr",
onShow: function (ct) {
this.setOptions({
maxDate: new Date()
});
},
timepicker: false,
format: "Y-m-d",
onSelectDate: function (ct, $i) {
var startDate = $(startSelector).datetimepicker("getValue");
var endDate = $(endSelector).datetimepicker("getValue");
if (!startDate) {
var newStartDate = new Date(endDate);
newStartDate.setDate(endDate.getDate() - maxRangeDays);
$(startSelector).val(formatDate(newStartDate));
} else {
var dayDifference = (endDate - startDate) / (1000 * 60 * 60 * 24);
if (dayDifference > maxRangeDays) {
var newStartDate = new Date(endDate);
newStartDate.setDate(endDate.getDate() - maxRangeDays);
$(startSelector).val(formatDate(newStartDate));
}
}
}
});
}
function initializeScatterChart() {
bindStartDatePicker("#scatter_start_date", "#scatter_end_date", 30);
bindEndDatePicker("#scatter_start_date", "#scatter_end_date", 30);
}
function initializeMultiStackChart() {
bindStartDatePicker("#multi_stack_start_date", "#multi_stack_end_date", 30);
bindEndDatePicker("#multi_stack_start_date", "#multi_stack_end_date", 30);
}
function initializeTimelineChart() {
bindStartDatePicker("#timeline_start_date", "#timeline_end_date", 180);
bindEndDatePicker("#timeline_start_date", "#timeline_end_date", 180);
}
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/versions-with-date?c_id=" + pdserviceId,
type: "GET",
contentType: "application/json;charset=UTF-8",
dataType: "json",
progress: true,
async: false,
statusCode: {
200: function (json) {
let versionData = json.response;
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,
ratio: 0
};
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);
});
console.log("versionGauge => ", versionGauge);
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("
' + "일별 업데이트 상태 차트를 로딩 중입니다..."
);
$.ajax({
url: "/auth-admin/api/arms/analysis/time/scatter-data",
type: "POST",
data: JSON.stringify({
pdServiceAndIsReq: {
pdServiceLink: pdServiceLink,
pdServiceVersionLinks: pdServiceVersionLinks.split(",").map(Number)
},
startDate: startDate,
endDate: endDate
}),
contentType: "application/json;charset=UTF-8",
dataType: "json",
progress: true,
async: false,
statusCode: {
200: function (data) {
console.log("[ analysisTime :: dailyUpdatedStatusScatterChart ] :: 일별 업데이트 상태 스캐터 차트 데이터 = ", data);
let result = data.reduce(
(acc, entry) => {
if (entry.requirement !== 0 || entry.relationIssue !== 0) {
acc.dates.push(entry.date);
acc.requirement.push(entry.requirement);
acc.relationIssue.push(entry.relationIssue);
}
return acc;
},
{
dates: [],
requirement: [],
relationIssue: []
}
);
let dates = result.dates;
let totalRequirements = result.requirement;
let totalRelationIssues = result.relationIssue;
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" }
},
grid: {
left: "3%",
right: "3%",
bottom: "1%",
containLabel: true
},
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: { show: false },
symbolSize: function (val) {
return val > 10 ? val * 1.1 : val === 0 ? 0 : 10;
}
},
{
name: "연결된 이슈",
data: totalRelationIssues,
type: "scatter",
clip: false,
label: { show: false },
symbolSize: function (val) {
return val > 10 ? val * 1.1 : val === 0 ? 0 : 10;
},
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.setOption(option, true);
adjustScatterChartHeight();
myChart.on("mouseover", function (params) {
if (params.seriesType === "scatter" && params.seriesIndex !== undefined) {
var option = myChart.getOption();
var series = option.series[params.seriesIndex];
if (series) {
series.label.show = false;
myChart.setOption(option, false, true);
}
}
});
myChart.on("mouseout", function (params) {
if (params.seriesType === "scatter" && params.seriesIndex !== undefined) {
var option = myChart.getOption();
var series = option.series[params.seriesIndex];
if (series) {
series.label.show = false;
myChart.setOption(option, false, true);
}
}
});
} else {
option = {
title: {
text: "데이터가 없습니다.",
left: "center",
top: "middle",
textStyle: {
color: "#fff",
fontFamily: "Nanum Gothic",
fontWeight: "normal",
fontSize: "13px"
}
},
backgroundColor: "rgba(255,255,255,0)"
};
myChart.setOption(option, true);
}
window.addEventListener("resize", () => {
myChart.resize();
adjustScatterChartHeight();
});
}
}
});
}
////////////////////
// 히트맵 차트
////////////////////
function calendarHeatMap(pdServiceLink, pdServiceVersionLinks) {
$("#calendar_yearview_blocks_chart_1 svg").remove();
$("#calendar_yearview_blocks_chart_2 svg").remove();
$.ajax({
url: "/auth-admin/api/arms/analysis/time/heatmap-data",
type: "POST",
data: JSON.stringify({
pdServiceAndIsReq: {
pdServiceLink: pdServiceLink,
pdServiceVersionLinks: pdServiceVersionLinks.split(",").map(Number)
}
}),
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"]
});
}
}
});
}
function adjustScatterChartHeight() {
const heatmapBody = document.getElementById("heatmap-body");
const scatterChartContainer = document.getElementById("scatter-chart-container");
if (!heatmapBody || !scatterChartContainer) return;
setTimeout(() => {
const heatmapHeight = heatmapBody.offsetHeight;
const adjustedHeight = heatmapHeight > 415 ? heatmapHeight - 49 : 366;
scatterChartContainer.style.minHeight = `${adjustedHeight}px`;
scatterChartContainer.style.height = `${adjustedHeight}px`;
}, 10);
}
////////////////
// 멀티 콤비네이션 차트
///////////////
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;
}
$(".spinner").html(
'
' +
"생성 개수 및 업데이트 상태 현황 정보를 로딩 중입니다..."
);
$.ajax({
url: "/auth-admin/api/arms/analysis/time/updated-issue/multi-combination-data",
type: "POST",
data: JSON.stringify({
pdServiceAndIsReq: {
pdServiceLink: pdServiceLink,
pdServiceVersionLinks: pdServiceVersionLinks.split(",").map(Number)
},
startDate: startDate,
endDate: endDate
}),
contentType: "application/json;charset=UTF-8",
dataType: "json",
progress: true,
async: false,
statusCode: {
200: function (data) {
console.log(
"[ analysisTime :: dailyCreatedCountAndUpdatedStatusesMultiStackCombinationChart ] :: 일별 이슈 생성 개수 및 업데이트 현황 데이터 => ", data
);
var accumulateRequirementCount = 0;
var accumulateRelationIssueCount = 0;
let result = data.reduce(
(acc, item) => {
if (
item.totalRequirements !== 0 ||
item.totalRelationIssues !== 0 ||
item.requirementStatuses.length > 0 ||
item.relationIssueStatuses.length > 0
) {
acc.dates.push(item.date);
accumulateRequirementCount += item.totalRequirements;
accumulateRelationIssueCount += item.totalRelationIssues;
acc.totalRequirements.push(accumulateRequirementCount);
acc.totalRelationIssues.push(accumulateRelationIssueCount);
}
item.requirementStatuses.forEach((statusObj) => {
if (!acc.statusKeys.includes(statusObj.status)) {
acc.statusKeys.push(statusObj.status);
}
});
item.relationIssueStatuses.forEach((statusObj) => {
if (!acc.statusKeys.includes(statusObj.status)) {
acc.statusKeys.push(statusObj.status);
}
});
return acc;
},
{
dates: [],
totalRequirements: [],
totalRelationIssues: [],
statusKeys: []
}
);
var dom = document.getElementById("multi-chart-container");
var myChart = echarts.init(dom, null, {
renderer: "canvas",
useDirtyRect: false
});
var option;
if (result && 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 totalRequirements = result.totalRequirements;
let totalRelationIssues = result.totalRelationIssues;
let statusKeys = result.statusKeys;
let requirementStatusSeries = statusKeys.map((key) => {
let stackType = "요구사항";
return {
name: key,
type: "bar",
stack: stackType,
label: labelOption,
emphasis: {
focus: "series"
},
data: dates.map((date) => {
let item = data.find((d) => d.date === date);
let status = item.requirementStatuses.find((s) => s.status === key);
return { value: status ? status.count : 0, stackType: stackType };
})
};
});
let relationIssueStatusSeries = statusKeys.map((key) => {
let stackType = "연결된 이슈";
return {
name: key,
type: "bar",
stack: stackType,
label: labelOption,
emphasis: {
focus: "series"
},
data: dates.map((date) => {
let item = data.find((d) => d.date === date);
let status = item.relationIssueStatuses.find((s) => s.status === key);
return { value: status ? status.count : 0, stackType: stackType };
})
};
});
statusKeys.push("요구사항");
statusKeys.push("연결된 이슈");
let multiCombinationChartSeries = [
...requirementStatusSeries,
...relationIssueStatusSeries,
{
name: "요구사항",
type: "line",
emphasis: {
focus: "series"
},
symbolSize: 10,
data: totalRequirements
},
{
name: "연결된 이슈",
type: "line",
emphasis: {
focus: "series"
},
symbolSize: 10,
data: totalRelationIssues
}
];
var legendData = statusKeys;
var xAiasData = dates;
option = {
tooltip: {
trigger: "axis",
axisPointer: {
type: "shadow"
},
formatter: function (params) {
var tooltipText = "";
tooltipText += params[0].axisValue + "
' + "수직 타임라인 차트를 로딩 중입니다..."
);
$.ajax({
url: "/auth-admin/api/arms/analysis/time/updated-timeline",
type: "POST",
data: JSON.stringify({
pdServiceAndIsReq: {
pdServiceLink: pdServiceLink,
pdServiceVersionLinks: pdServiceVersionLinks.split(",").map(Number)
},
startDate: startDate,
endDate: endDate
}),
contentType: "application/json;charset=UTF-8",
dataType: "json",
progress: true,
statusCode: {
200: function (data) {
console.log("[ analysisTime :: TimeLineData ] :: = ");
console.log(data);
verticalTimeLineChart(data);
}
}
});
var traffic;
function executeAjaxCall() {
$(".spinner").html(
'
' +
"요구사항 업데이트 현황(능선차트)를 로딩 중입니다..."
);
$.ajax({
url: "/auth-admin/api/arms/analysis/time/updated-ridgeline",
type: "POST",
contentType: "application/json;charset=UTF-8",
dataType: "json",
data: JSON.stringify({
pdServiceAndIsReq: {
pdServiceLink: pdServiceLink,
pdServiceVersionLinks: pdServiceVersionLinks.split(",").map(Number)
},
startDate: startDate,
endDate: endDate
}),
progress: true,
statusCode: {
200: function (data) {
console.log("[ analysisTime :: ridgeLineData ] :: = ", data.response);
traffic = data.response;
updateRidgeLine(data.response);
}
}
});
}
executeAjaxCall();
window.addEventListener("resize", function () {
adjustHeight();
updateRidgeLine(traffic);
});
}
function getColorByVersion(version) {
var colorPalette = [
//e chart 컬러 팔레트
"#61a0a8",
"#d48265",
"#91c7ae",
"#749f83",
"#ca8622",
"#bda29a",
"#6e7074",
"#546570",
"#c4ccd3",
"#c23531",
"#2f4554",
];
var versionNumber = parseInt(version);
if (isNaN(versionNumber)) {
return "#c4ccd3"; // 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.updateDate); }) .entries(traffic); var dates = nestedDataByDate .map(function (d) { return +d.key; }) .sort(d3.ascending); var nestedDataByName = d3 .nest() .key(function (d) { return d.summary; }) .entries(traffic); var series = nestedDataByName.map(function (d) { var valuesMap = d3.map(d.values, function (e) { return String(+new Date(e.updateDate)); }); var values = dates.map(function (updateDate) { var valueObj = valuesMap.get(String(updateDate)); return valueObj ? valueObj.updatedCount : null; }); var version = d.values[0] ? d.values[0].version : null; // version 필드 추가 var summary = d.values[0] ? d.values[0].summary : null; // summary 필드 추가 var key = d.values[0] ? d.values[0].key : null; // key 필드 추가 return { name: key + ": " + summary, values: values, version: version, key: key }; // version 값 포함하여 반환 }); //const overlap = 4; //const height = series.length * 30; //const height = Math.max(minHeight, Math.min(maxHeight, series.length * 16)); const width = Math.max(parseInt($("#updateRidgeLine").width()) - 15, 655); const marginTop = 50; const marginRight = 0; const marginBottom = 20; //const marginLeft = 280; const displayCount = series.length; const rowHeight = 30; const height = displayCount * rowHeight + marginTop + marginBottom; const marginLeft = calculateDynamicMarginLeft( getMarginSafeLabels( series.map((d) => d.name), 42 ), 12, 20 ); // 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", height); // 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", "13px"); // 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) .style("display", "none") .style("pointer-events", "none"); 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("display", "block").style("opacity", 0.9); div .html( "버전 정보: " + convertVersionIdsToTitle(d.version) + "