var SankeyChart = ( function ($) { "use strict"; var target = { "$targetId": "", // jQuery :: $ + targetId "targetId" : "" // targetId :: 차트를 그릴 element id }; var setTarget = function (targetElementId) { target.$targetId = "#" + targetElementId; target.targetId = targetElementId; }; var initSvg = function () { var margin = { top: 10, right: 10, bottom: 10, left: 10 }; var width = document.getElementById(target.targetId).offsetWidth; var height = document.getElementById(target.targetId).offsetHeight - margin.top - margin.bottom; var vx = width + margin.left + margin.right; var vy = height + margin.top + margin.bottom; return d3 .select(target.$targetId) // 그려지는 위치 .append("svg") .attr("viewBox", "0 0 " + vx + " " + vy) .attr("width", width) .attr("height", height) .append("g") .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); }; var drawEmptyChart = function () { var margin = { top: 10, right: 10, bottom: 10, left: 10 }; var width = document.getElementById(target.targetId).offsetWidth - margin.left - margin.right; var height = 500 - margin.top - margin.bottom; initSvg() .append("text") .style("font-size", "12px") .style("fill", "white") .style("font-weight", 5) .text("선택된 어플리케이션이 없습니다.") .attr("x", width / 2) .attr("y", height / 2); }; var loadChart = function (data, targetElementId) { if(targetElementId) { setTarget(targetElementId); } var margin = { top: 10, right: 10, bottom: 10, left: 10 }; var width = document.getElementById(target.targetId).offsetWidth - margin.left - margin.right; var height = 500 - margin.top - margin.bottom; var formatNumber = d3.format(",.0f"); var format = function (d) { return formatNumber(d); }; var iconXs = [10, 12, 11.5, 12]; var nodeIcons = ['', '', '']; var colors = ColorPalette.d3Chart.sankeyChart; var svg = initSvg(); if (isEmpty(data.nodes)) { svg .append("text") .style("font-size", "12px") .style("fill", "white") .style("font-weight", 5) .text("해당 프로젝트에 매핑된 버전이 없습니다.") .attr("x", width / 2) .attr("y", height / 2); return; } var sankey = d3.sankey() .nodeWidth(36) .nodePadding(40) .size([width, height]); var graph = { nodes: [], links: [] }; var nodeMap = { }; var color = d3.scaleOrdinal(colors); var iconX = d3.scaleOrdinal(iconXs); var nodeIcon = d3.scaleOrdinal(nodeIcons); data.nodes.forEach(function (nodeInfos) { graph.nodes.push(nodeInfos); }); data.links.forEach(function (nodeLinks) { graph.links.push(nodeLinks); }); graph.nodes.forEach(function (node) { nodeMap[node.id] = node; }); graph.links = graph.links.map(function (link) { return { source: nodeMap[link.source], target: nodeMap[link.target], value: 1 }; }); graph = sankey(graph); var link = svg .append("g") .selectAll(".link") .data(graph.links) .enter() .append("path") .attr("class", "link") .attr("d", d3.sankeyLinkHorizontal()) .attr("stroke-width", function (d) { return d.width; }); link.append("title").text(function (d) { return d.source.name + " → " + d.target.name + "\n" + format(d.value); }); var node = svg.append("g").selectAll(".node").data(graph.nodes).enter().append("g").attr("class", "node"); node .append("rect") .attr("x", function (d) { return d.x0; }) .attr("y", function (d) { return d.y0; }) .attr("height", function (d) { return d.y1 - d.y0; }) .attr("width", sankey.nodeWidth()) .style("fill", function (d) { return (d.color = color(d.type)); }) .style("cursor", "default") .style("stroke", function (d) { return d3.rgb(d.color).darker(2); }) .append("title") .text(function (d) { return d.name + "\n" + format(d.value); }); node .append("svg:foreignObject") .attr("x", function (d) { return d.x0 + iconX(d.type); }) .attr("y", function (d) { return (d.y1 + d.y0 - 16) / 2; }) .attr("height", "16px") .attr("width", "16px") .style("cursor", "default") .html((d) => nodeIcon(d.type)); node .append("text") .attr("x", function (d) { return d.x0 > 0 ? d.x0 - 6 : d.x1 - d.x0 + 6; }) .attr("y", function (d) { return (d.y1 + d.y0 - 18) / 2; }) .attr("dy", "0.35em") .style("fill", function (d) { return (d.color = color(d.type)); }) .style("text-shadow", function (d) { return `0 1px 0 ${(d.color = color(d.type))}`; }) .attr("text-anchor", function (d) { return d.x0 > 0 ? "end" : "start"; }) .text(function (d) { return `[${d.type}]`; }) .filter(function (d) { return d.x < width / 2; }) .attr("x", 6 + sankey.nodeWidth()) .attr("text-anchor", "start"); node .append("text") .attr("x", function (d) { return d.x0 > 0 ? d.x0 - 6 : d.x1 - d.x0 + 6; }) .attr("y", function (d) { return (d.y1 + d.y0 + 18) / 2; }) .attr("dy", ".35em") .attr("text-anchor", function (d) { return d.x0 > 0 ? "end" : "start"; }) .attr("transform", null) .text(function (d) { return d.name; }) .filter(function (d) { return d.x < width / 2; }) .attr("x", 6 + sankey.nodeWidth()) .attr("text-anchor", "start"); }; return { loadChart, drawEmptyChart }; })(jQuery); //////////////////////////////////////////////////////////////////////////////////////// // 제품-버전-투입인력 차트 생성 //////////////////////////////////////////////////////////////////////////////////////// // 교체 예정 function drawProductToManSankeyChart(pdServiceLink, pdServiceVersionLinks, targetId, 하위_크기) { let $target = "#"+targetId; function removeSankeyChart() { const svgElement = d3.select($target).select("svg"); if (!svgElement.empty()) { svgElement.remove(); } } const url = new UrlBuilder() .setBaseUrl('/auth-user/api/arms/dashboard/version-assignees') .addQueryParam('pdServiceLink', pdServiceLink) .addQueryParam('pdServiceVersionLinks', pdServiceVersionLinks) .addQueryParam('하위_크기', 하위_크기) .build(); $.ajax({ url: url, type: "GET", contentType: "application/json;charset=UTF-8", dataType: "json", progress: true, statusCode: { 200: function (apiResponse) { removeSankeyChart(); const data = apiResponse.response; SankeyChart.loadChart(data, targetId); } } }); } // Resource Sankey-Chart function versionAssigneesSankeyChart(pdServiceLink, pdServiceVersionLinks, targetId) { let $target = "#"+targetId; function removeSankeyChart() { const svgElement = d3.select($target).select("svg"); if (!svgElement.empty()) { svgElement.remove(); } } $.ajax({ url: "/auth-admin/api/arms/analysis/resource/version-assignees", type: "GET", contentType: "application/json;charset=UTF-8", data: { "pdServiceAndIsReq.pdServiceLink" : pdServiceLink, "pdServiceAndIsReq.pdServiceVersionLinks" : pdServiceVersionLinks, }, dataType: "json", progress: true, statusCode: { 200: function (apiResponse) { removeSankeyChart(); const data = apiResponse.response; SankeyChart.loadChart(data, targetId); } } }); }