var selectedJsTreeId; // 현재 선택된 jsTree 노드의 ID (node_ 접두사 제거된 순수 ID) // 4분면 색상 상수: [긴급+중요, 비긴급+중요, 비긴급+비중요, 긴급+비중요] const QUADRANT_COLORS = ["rgb(255, 77, 77)", "rgb(77, 255, 77)", "rgb(61, 61, 61)", "rgb(179, 115, 0)"]; const ReqUrgency = { 매우_긴급: 3, 긴급: 4, 보통: 5, 검토: 6, 보류: 7 }; const ReqImportance = { 매우_중요: 3, 중요: 4, 보통: 5, 낮음: 6, 매우_낮음: 7 }; const CHART_HEIGHT = 800; const MATRIX_MARGIN = 40; const CONTAINER_PADDING = 40; var reqEntityList = []; var circlesGroup = null; var getChartWidth = null; function execDocReady() { var pluginGroups = [ [ "../reference/lightblue4/docs/lib/slimScroll/jquery.slimscroll.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/swiper-11.1.4/swiper-bundle.min.js", "../reference/jquery-plugins/swiper-11.1.4/swiper-bundle.min.css", "./js/common/swiperHelper.js", "./css/customSwiper.css", "./js/reqAddTable.js" ], [ "../reference/jquery-plugins/select2-4.0.2/dist/css/select2_lightblue4.css", "../reference/jquery-plugins/select2-4.0.2/dist/js/select2.min.js", "../reference/jquery-plugins/lou-multi-select-0.9.12/css/multiselect-lightblue4.css", "../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-bluelight.css", "../reference/jquery-plugins/multiple-select-1.5.2/dist/multiple-select.min.js" ], [ "../reference/jquery-plugins/jstree-v.pre1.0/_lib/jquery.cookie.js", "../reference/jquery-plugins/jstree-v.pre1.0/_lib/jquery.hotkeys.js", "../reference/jquery-plugins/jstree-v.pre1.0/jquery.jstree.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" ], [ "../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/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/datetimepicker-2.5.20/build/jquery.datetimepicker.min.css", "../reference/light-blue/lib/bootstrap-datepicker.js", "../reference/jquery-plugins/datetimepicker-2.5.20/build/jquery.datetimepicker.full.min.js", "../reference/lightblue4/docs/lib/widgster/widgster.js", "../reference/jquery-plugins/d3-6.7.0/d3.min.js", "../reference/lightblue4/docs/lib/slimScroll/jquery.slimscroll.min.js", "../reference/jquery-plugins/timerStyles.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", "../reference/jquery-plugins/swiper-11.1.4/swiper-bundle.min.js", "../reference/jquery-plugins/swiper-11.1.4/swiper-bundle.min.css", "./js/common/swiperHelper.js", "./css/customSwiper.css", "./js/reportSWOT/reqAddList.js" ] ]; loadPluginGroupsParallelAndSequential(pluginGroups) .then(function () { $(".widget").widgster(); setSideMenu("sidebar_menu_analysis", "sidebar_menu_report_swot"); var waitSelect2 = setInterval(function () { try { if ($(".ms-select-all") !== 3) { makePdServiceSelectBox(); // reqAddList.js makeVersionMultiSelectBox(); // reqAddList.js clearInterval(waitSelect2); // 초기화 성공 시 polling 중단 } } catch (err) { console.log("서비스 데이터 테이블 로드가 완료되지 않아서 초기화 재시도 중..."); } }, 313 /*milli*/); initSWOTChart(); disableRadioButtons(); }) .catch(function () { console.error("플러그인 로드 중 오류 발생"); }); } function disableRadioButtons() { $(".btn-disabled").each(function () { // input[type='radio'] 요소에 disabled 속성 추가 → 브라우저 기본 비활성화 $(this).find("input[type='radio']").prop("disabled", true); // 부모 컨테이너에 pointer-events: none 적용 → 라벨 클릭으로 우회하는 것도 방지 $(this).css("pointer-events", "none"); }); } function updateFunctionPointList() { const serviceId = $("#selected_pdService").val(); if (!serviceId) { return; } $.ajax({ url: "/auth-user/api/arms/reqAdd/T_ARMS_REQADD_" + serviceId + "/getChildNode.do?c_id=2", type: "GET", contentType: "application/json;charset=UTF-8", dataType: "json", progress: true }) .done(function (data) { if (data && data.length > 0) { reqEntityList = data; var usedPositions = {}; reqEntityList.forEach((d) => { const urgency = d.reqUrgencyEntity?.c_id; const importance = d.reqImportanceEntity?.c_id; const difficulty = d.reqDifficultyEntity?.c_id; const { px, py } = calcRelativePosition(urgency, importance, usedPositions); d.px = px; d.py = py; d.c_req_priority_value = calculatePriorityValue(urgency, importance, difficulty); }); renderFunctionPoints(); } }) .fail(function (jqXHR, textStatus, errorThrown) { console.error("데이터 가져오기 오류:", textStatus, errorThrown); renderFunctionPoints(); }); } function renderFunctionPoints() { const width = getChartWidth(); const height = CHART_HEIGHT - 100; const MATRIX_X = MATRIX_MARGIN; const MATRIX_Y = MATRIX_MARGIN; if (!width || isNaN(width)) return; // ── d3 드래그 동작 정의 ──────────────────────────────────────────────────── const drag = d3 .drag() .on("start", function (event, d) { d.__dragged = false; d.__startZoneKey = Math.floor(d.px * 5) + "," + Math.floor(d.py * 5); circlesGroup .selectAll(".draggable-circle") .attr("r", 7) .attr("stroke", null) .attr("stroke-width", null) .each(function (od) { od.__selected = false; }); d.__selected = true; }) .on("drag", function (event, d) { d.__dragged = true; const [pointerX, pointerY] = d3.pointer(event, d3.select("#matrix").node()); d.px = Math.max(0, Math.min(1, (pointerX - MATRIX_X) / getChartWidth())); d.py = Math.max(0, Math.min(1, (pointerY - MATRIX_Y) / height)); d.cx = MATRIX_X + d.px * getChartWidth(); d.cy = MATRIX_Y + d.py * height; d3.select(this).attr("cx", d.cx).attr("cy", d.cy).attr("fill", getQuadrantColor(d.px, d.py)); if (d.__crowdedIcon) { d.__crowdedIcon.attr("x", d.cx).attr("y", d.cy); } }) .on("end", function (event, d) { console.log("[drag end] c_id:", d.c_id, "__dragged:", d.__dragged, "selectTreeNodeByReqId 존재:", typeof selectTreeNodeByReqId === "function"); if (!d.__dragged) { console.log("[클릭 감지] c_id:", d.c_id); selectedJsTreeId = String(d.c_id); updateReqAddDetail(); onFocusFunctionPoint(selectedJsTreeId); if (typeof selectTreeNodeByReqId === "function") { selectTreeNodeByReqId(d.c_id); } return; } // px 구간(0.2 단위 5열) → 긴급도 값 역산 const urgencyByCol = [ReqUrgency.보류, ReqUrgency.검토, ReqUrgency.보통, ReqUrgency.긴급, ReqUrgency.매우_긴급]; if (!d.reqUrgencyEntity) { d.reqUrgencyEntity = {}; } d.reqUrgencyEntity.c_id = urgencyByCol[Math.min(4, Math.floor(d.px * 5))]; d.c_req_priority_value = calculatePriorityValue( d.reqUrgencyEntity.c_id, d.reqImportanceEntity?.c_id, d.reqDifficultyEntity?.c_id ); var newZoneKey = Math.floor(d.px * 5) + "," + Math.floor(d.py * 5); if (newZoneKey !== d.__startZoneKey) { var othersInZone = reqEntityList.filter((e) => { var k = Math.floor(e.px * 5) + "," + Math.floor(e.py * 5); return k === newZoneKey && e.c_id !== d.c_id; }).length; if (othersInZone >= 10) { if (!d.__crowdedIcon) { d.__crowdedIcon = circlesGroup.append("text") .attr("x", d.cx).attr("y", d.cy) .attr("class", "crowded-icon") .attr("text-anchor", "middle") .attr("dominant-baseline", "central") .attr("pointer-events", "none") .text("!"); } } else { if (d.__crowdedIcon) { d.__crowdedIcon.remove(); d.__crowdedIcon = null; } } } }); if (!reqEntityList || reqEntityList.length === 0) { circlesGroup.selectAll(".draggable-circle, .crowded-icon").remove(); return; } var zoneOrderMap = {}; var renderUsedPositions = {}; reqEntityList.forEach((d) => { if (d.px === undefined || isNaN(d.px) || d.py === undefined || isNaN(d.py)) { const pos = calcRelativePosition(d.reqUrgencyEntity?.c_id, d.reqImportanceEntity?.c_id, renderUsedPositions); d.px = pos.px; d.py = pos.py; } d.cx = MATRIX_X + d.px * width; d.cy = MATRIX_Y + d.py * height; var zoneKey = Math.floor(d.px * 5) + "," + Math.floor(d.py * 5); zoneOrderMap[zoneKey] = (zoneOrderMap[zoneKey] || 0) + 1; d.__zoneOrder = zoneOrderMap[zoneKey]; }); // circles: join 패턴으로 재렌더 시에도 이벤트 유지 circlesGroup.selectAll(".draggable-circle") .data(reqEntityList, (d) => d.c_id) .join( (enter) => enter.append("circle") .attr("class", "draggable-circle") .style("cursor", "pointer") .call(drag) .each(function(d) { d3.select(this).append("title").text(d.c_title); }), (update) => update, (exit) => exit.remove() ) .attr("cx", (d) => d.cx) .attr("cy", (d) => d.cy) .attr("r", 7) .attr("fill", (d) => getQuadrantColor(d.px, d.py)) .attr("data-id", (d) => d.c_id); // crowded icons: join 패턴으로 11번째 이후 점에만 표시 var crowdedList = reqEntityList.filter((d) => d.__zoneOrder > 10); circlesGroup.selectAll(".crowded-icon") .data(crowdedList, (d) => d.c_id) .join( (enter) => enter.append("text") .attr("class", "crowded-icon") .attr("text-anchor", "middle") .attr("dominant-baseline", "central") .attr("pointer-events", "none") .text("!"), (update) => update, (exit) => exit.remove() ) .attr("x", (d) => d.cx) .attr("y", (d) => d.cy) .each(function(d) { d.__crowdedIcon = d3.select(this); }); } function repositionCircles() { const width = getChartWidth(); const height = CHART_HEIGHT - 100; circlesGroup.selectAll(".draggable-circle").each(function (d) { d.cx = MATRIX_MARGIN + d.px * width; d.cy = MATRIX_MARGIN + d.py * height; d3.select(this).attr("cx", d.cx).attr("cy", d.cy); if (d.__crowdedIcon) { d.__crowdedIcon.attr("x", d.cx).attr("y", d.cy); } }); } // 상대좌표(px, py)로 현재 사분면의 색상을 반환 // px > 0.5 = 긴급, py < 0.5 = 중요 (SVG Y축 반전) function getQuadrantColor(px, py) { if (px > 0.5 && py < 0.5) return QUADRANT_COLORS[0]; // 긴급 + 중요 if (px <= 0.5 && py < 0.5) return QUADRANT_COLORS[1]; // 비긴급 + 중요 if (px <= 0.5 && py >= 0.5) return QUADRANT_COLORS[2]; // 비긴급 + 비중요 return QUADRANT_COLORS[3]; // 긴급 + 비중요 } function calcRelativePosition(urgency, importance, usedPositions) { var urgencyFib = convertToFibonacci(urgency); var importanceFib = convertToFibonacci(importance); var basePx = 0.1 + (urgencyFib / 8) * 0.8; var basePy = 0.1 + (1 - importanceFib / 8) * 0.8; var JITTER = 0.04; var D = 0.707; // 대각선 보정 (1/√2) var offsets = [{dx: 0, dy: 0}]; for (var ring = 1; ring <= 5; ring++) { var r = ring * JITTER; var g = ring * JITTER * D; offsets.push( {dx: r, dy: 0}, {dx: -r, dy: 0}, {dx: 0, dy: r}, {dx: 0, dy: -r}, {dx: g, dy: g}, {dx: -g, dy: g}, {dx: g, dy: -g}, {dx: -g, dy: -g} ); } for (var i = 0; i < offsets.length; i++) { var px = Math.max(0.02, Math.min(0.98, basePx + offsets[i].dx)); var py = Math.max(0.02, Math.min(0.98, basePy + offsets[i].dy)); var key = px.toFixed(4) + "," + py.toFixed(4); if (!usedPositions[key]) { usedPositions[key] = true; return { px: px, py: py }; } } return { px: Math.max(0.02, Math.min(0.98, basePx)), py: Math.max(0.02, Math.min(0.98, basePy)) }; } function initSWOTChart() { const svg = d3.select("#matrix"); const wrapper = $("#matrix_wrapper"); // skeleton: 리사이즈마다 재렌더링 (축·레이블·구분선) // circles: 데이터 로드 시 1회 생성, 리사이즈 시 위치만 갱신 const skeletonGroup = svg.append("g").attr("class", "skeleton-group"); circlesGroup = svg.append("g").attr("class", "circles-group"); getChartWidth = function () { return wrapper.width() - CONTAINER_PADDING; }; function render() { const width = getChartWidth(); svg .attr("viewBox", `0 0 ${width + MATRIX_MARGIN * 2} ${CHART_HEIGHT}`) .attr("preserveAspectRatio", "xMidYMid meet") .attr("width", "100%") .attr("height", CHART_HEIGHT); skeletonGroup.selectAll("*").remove(); drawMatrixChart(skeletonGroup, width, CHART_HEIGHT - 100, MATRIX_MARGIN); } render(); updateFunctionPointList(); // .off("change") 로 기존 핸들러를 먼저 제거하여 이벤트가 중복 등록되는 것을 방지. $("#selected_pdService").off("change").on("change", updateFunctionPointList); window.addEventListener("resize", function () { render(); repositionCircles(); }); } function drawMatrixChart(svg, width, height, margin) { const MATRIX_X = margin; // 매트릭스 좌측 경계 x 좌표 const MATRIX_Y = margin; // 매트릭스 상단 경계 y 좌표 const BORDER_X = 25; // Y축(세로선) x 좌표 — 화살표 보조선 위치 기준 const BORDER_Y = 25; // X축(가로선) y 오프셋 — 화살표 보조선 위치 기준 // ── 4분면 레이블 정의 ────────────────────────────────────────────────────── const priority = [ { x: MATRIX_X + width / 2, y: MATRIX_Y, color: QUADRANT_COLORS[0], text: "긴급하고 중요한 일", index: 1 }, { x: MATRIX_X + width / 2, y: MATRIX_Y + height / 2, color: QUADRANT_COLORS[3], text: "긴급하지만 중요하지 않은 일", index: 2 }, { x: MATRIX_X, y: MATRIX_Y, color: QUADRANT_COLORS[1], text: "긴급하지 않지만 중요한 일", index: 3 }, { x: MATRIX_X, y: MATRIX_Y + height / 2, color: QUADRANT_COLORS[2], text: "긴급하지도 중요하지도 않은 일", index: 4 } ]; // ── 외곽 사각형 (매트릭스 전체 테두리) ──────────────────────────────────── svg .append("rect") .attr("x", MATRIX_X) .attr("y", MATRIX_Y) .attr("width", width) .attr("height", height) .attr("rx", 8) // 모서리 둥글게 .attr("class", "rectangle"); // ── 4분면 레이블 렌더링 ──────────────────────────────────────────────────── priority.forEach((q) => { // 사분면 색상 표시 박스 (30×30px, 반투명) svg .append("rect") .attr("x", q.x + 10) // 사분면 코너에서 10px 우측 오프셋 .attr("y", q.y + 10) // 사분면 코너에서 10px 하단 오프셋 .attr("width", 30) .attr("height", 30) .attr("rx", 8) .attr("fill", q.color) .attr("opacity", 0.4); // 반투명으로 배경이 비치게 처리 // 사분면 번호 텍스트 (색상 박스 위에 표시) svg .append("text") .attr("x", q.x + 21) // 색상 박스 수평 중앙 (10 + 30/2 - 약간 보정) .attr("y", q.y + 30) // 색상 박스 수직 중앙 .attr("class", "text") .text(q.index); // 사분면 설명 텍스트 (색상 박스 우측에 배치) svg .append("text") .attr("x", q.x + 45) // 색상 박스(30px) + 여백(5px) 이후 .attr("y", q.y + 30) .attr("class", "text") .text(q.text); }); // ── 축 경계선 정의 ───────────────────────────────────────────────────────── // class="border" CSS 클래스로 스타일 일괄 적용 const borders = [ // Y축 본선: 세로 방향 (중요도 축) — 좌측에 위치 { x1: BORDER_X, y1: MATRIX_Y, x2: BORDER_X, y2: MATRIX_Y + height }, // Y축 화살표 보조선: 상단 끝에서 왼쪽 대각선으로 화살표 표현 { x1: BORDER_X, y1: MATRIX_Y, x2: BORDER_X - 10, y2: MATRIX_Y + 10 }, // X축 본선: 가로 방향 (긴급도 축) — 하단에 위치 (height + BORDER_Y 로 매트릭스 아래) { x1: MATRIX_X, y1: MATRIX_Y + BORDER_Y + height, x2: MATRIX_X + width, y2: MATRIX_Y + BORDER_Y + height }, // X축 화살표 보조선: 우측 끝에서 오른쪽 아래 대각선으로 화살표 표현 { x1: MATRIX_X + width, y1: MATRIX_Y + BORDER_Y + height, x2: MATRIX_X + width - 10, y2: MATRIX_Y + BORDER_Y + height + 10 }, // 25분면 수평 구분선 4개 (중요도 5단계 경계) { x1: MATRIX_X, y1: MATRIX_Y + height / 5, x2: MATRIX_X + width, y2: MATRIX_Y + height / 5 }, { x1: MATRIX_X, y1: MATRIX_Y + (height * 2) / 5, x2: MATRIX_X + width, y2: MATRIX_Y + (height * 2) / 5 }, { x1: MATRIX_X, y1: MATRIX_Y + (height * 3) / 5, x2: MATRIX_X + width, y2: MATRIX_Y + (height * 3) / 5 }, { x1: MATRIX_X, y1: MATRIX_Y + (height * 4) / 5, x2: MATRIX_X + width, y2: MATRIX_Y + (height * 4) / 5 }, // 25분면 수직 구분선 4개 (긴급도 5단계 경계) { x1: MATRIX_X + width / 5, y1: MATRIX_Y, x2: MATRIX_X + width / 5, y2: MATRIX_Y + height }, { x1: MATRIX_X + (width * 2) / 5, y1: MATRIX_Y, x2: MATRIX_X + (width * 2) / 5, y2: MATRIX_Y + height }, { x1: MATRIX_X + (width * 3) / 5, y1: MATRIX_Y, x2: MATRIX_X + (width * 3) / 5, y2: MATRIX_Y + height }, { x1: MATRIX_X + (width * 4) / 5, y1: MATRIX_Y, x2: MATRIX_X + (width * 4) / 5, y2: MATRIX_Y + height } ]; borders.forEach((b) => { svg.append("line").attr("class", "border").attr("x1", b.x1).attr("y1", b.y1).attr("x2", b.x2).attr("y2", b.y2); }); // "중요함": 상단 영역 (y 값이 작은 쪽) svg .append("text") .attr("x", -(MATRIX_X + height / 2 - height / 4)) // 회전 후 상단 1/4 지점 .attr("y", 20) // 좌측 여백 .attr("class", "text") .attr("text-anchor", "middle") .attr("transform", "rotate(-90)") .text("중요함"); // "중요하지 않음": 하단 영역 (y 값이 큰 쪽) svg .append("text") .attr("x", -(MATRIX_X + height / 2 + height / 4)) // 회전 후 하단 3/4 지점 .attr("y", 20) .attr("class", "text") .attr("text-anchor", "middle") .attr("transform", "rotate(-90)") .text("중요하지 않음"); // ── X축 긴급도 레이블 (차트 하단, 수평 배치) ───────────────────────────── // "긴급함": 우측 절반 (x > 중간선) svg .append("text") .attr("x", MATRIX_X + width / 2 + width / 4) // 우측 1/4 지점 (우측 영역 중앙) .attr("y", MATRIX_Y + height + 40) // 차트 하단 여백 아래 .attr("text-anchor", "middle") .attr("class", "text") .text("긴급함"); // "긴급하지 않음": 좌측 절반 (x < 중간선) svg .append("text") .attr("x", MATRIX_X + width / 2 - width / 4) // 좌측 1/4 지점 (좌측 영역 중앙) .attr("y", MATRIX_Y + height + 40) .attr("text-anchor", "middle") .attr("class", "text") .text("긴급하지 않음"); } function selectJsTreeNode() { $("#req_tree").jstree("select_node", selectedJsTreeId); $(".jstree-leaf a").removeClass("jstree-clicked"); $(".jstree-leaf[id='node_" + selectedJsTreeId + "']") .find("a") .addClass("jstree-clicked"); } /** * 중요:: 요구사항 노드를 클릭했을 때 액션을 정의한다. */ function jsTreeClick(selectedNode) { selectedJsTreeId = selectedNode.attr("id").replace("node_", "").replace("copy_", ""); selectJsTreeNode(); updateReqAddDetail(); onFocusFunctionPoint(selectedJsTreeId); } function updateReqAddDetail() { var tableName = "T_ARMS_REQADD_" + $("#selected_pdService").val(); $.ajax({ url: "/auth-user/api/arms/reqAdd/" + tableName + "/getNode.do?c_id=" + selectedJsTreeId, type: "GET", contentType: "application/json;charset=UTF-8", dataType: "json", progress: true }) .done(function (data) { bindDataDetailTab(data); }) .fail(function (e) {}) .always(function () {}); } // 요구사항 상세보기 function bindDataDetailTab(ajaxData) { $("#detailview_req_name").val(ajaxData.c_title); //radio 버튼 - 선택 초기화 $("#detailview_req_urgency label").removeClass("active"); $("#detailview_req_importance label").removeClass("active"); $("#detailview_req_difficulty label").removeClass("active"); $("#detailview_req_priority label").removeClass("active"); $("#detailview_req_state label").removeClass("active"); //radio 버튼 - 상태 초기화 $("input[name='detailview_req_urgency_options']:checked").prop("checked", false); $("input[name='detailview_req_importance_options']:checked").prop("checked", false); $("input[name='detailview_req_difficulty_options']:checked").prop("checked", false); $("input[name='detailview_req_priority_options']:checked").prop("checked", false); $("input[name='detailview_req_state_options']:checked").prop("checked", false); // 상세보기 - 긴급도 버튼 let urgencyRadioButtons = $("#detailview_req_urgency input[type='radio']"); urgencyRadioButtons.each(function () { if (ajaxData.reqUrgencyEntity && $(this).val() == ajaxData.reqUrgencyEntity.c_id) { $(this).parent().addClass("active"); $(this).prop("checked", true); } }); // 상세보기 - 중요도 버튼 let importanceRadioButtons = $("#detailview_req_importance input[type='radio']"); importanceRadioButtons.each(function () { if (ajaxData.reqImportanceEntity && $(this).val() == ajaxData.reqImportanceEntity.c_id) { $(this).parent().addClass("active"); $(this).prop("checked", true); } }); // 상세보기 - 난이도 버튼 let difficultyRadioButtons = $("#detailview_req_difficulty input[type='radio']"); difficultyRadioButtons.each(function () { if (ajaxData.reqDifficultyEntity && $(this).val() == ajaxData.reqDifficultyEntity.c_id) { $(this).parent().addClass("active"); $(this).prop("checked", true); } }); // 우선순위 자동 계산 값 설정 if (ajaxData.c_req_priority_value) { console.log(ajaxData.c_req_priority_value, "jinvicky"); $("#detailview_req_value").val(ajaxData.c_req_priority_value); } else { $("#detailview_req_value").val(""); } // 상세보기 - 상태 버튼 req_state_setting("detailview_req_state", true) .then(() => { let stateRadioButtons = $("#detailview_req_state input[type='radio']"); stateRadioButtons.each(function () { if (ajaxData.reqStateEntity && $(this).val() == ajaxData.reqStateEntity.c_id) { $(this).parent().addClass("active"); $(this).prop("checked", true); } else { $(this).prop("checked", false); } }); }) .catch((error) => { console.error("Error fetching data:", error); }); } function convertToFibonacci(value) { var fibonacciMap = { 3: 8, // 매우 긴급/중요/어려움 -> 8 4: 5, // 긴급/중요/어려움 -> 5 5: 3, // 보통 -> 3 6: 2, // 낮음/쉬움 -> 2 7: 1 // 매우 낮음/쉬움 -> 1 }; return fibonacciMap[value] || 0; } function calculatePriorityValue(urgency, importance, difficulty) { var urgencyFib = convertToFibonacci(urgency); var importanceFib = convertToFibonacci(importance); var difficultyFib = convertToFibonacci(difficulty); // 우선순위 = (긴급도 * 0.5) + (중요도 * 0.45) - (난이도 * 0.05) var priorityValue = urgencyFib * 0.5 + importanceFib * 0.45 - difficultyFib * 0.05; return Math.round(priorityValue * 100) / 100; } // jstree에서 노드를 클릭했을 때 일치하는 function point에 style을 추가한다. function onFocusFunctionPoint(selectedJsTreeId) { console.log("Propagate to SWOT called", selectedJsTreeId); if (!circlesGroup) return; selectJsTreeNode(); circlesGroup.selectAll(".draggable-circle").each(function (d) { if (d3.select(this).attr("data-id") === selectedJsTreeId) { circlesGroup.selectAll(".draggable-circle").attr("stroke-width", 0); d3.select(this).attr("stroke", "yellow").attr("stroke-width", 3); } }); }