//트리 생성 함수
let GenerateNodeTree = (graph) => {
//콘텐츠의 너비와 높이를 가져옴
var width= $(ccontentId).width();
var height= $(ccontentId).height();
var currentAlpha ;
//색상을 설정하는 함수
var color = d3.scaleOrdinal(colorArray);
//X와 Y 스케일 정의
var xScale = d3.scaleLinear().domain([0, width]).range([0, width]);
var yScale = d3.scaleLinear().domain([0, height]).range([0, height]);
//줌 기능 설정
var zoomer = d3.zoom()
.scaleExtent([0.1, 8])
.on("zoom", function(event) {
//현재 알파 값 저장 후 시뮬레이션 정지
currentAlpha = simulation.alpha();
simulation.stop();
var transform = event.transform;
//변환된 스케일을 업데이트함
var updatedXScale = transform.rescaleX(xScale);
var updatedYScale = transform.rescaleY(yScale);
//현재의 변환을 적용하여 노드 위치 업데이트
node.attr("transform", transform);
node.attr("transform", function(d) { return "translate(" + updatedXScale(d.x) + "," + updatedYScale(d.y) + ")"; });
//연결선의 위치도 업데이트
ticked = () =>{
link.attr("x1", function (d) { return updatedXScale(d.source.x); })
.attr("y1", function (d) { return updatedYScale(d.source.y); })
.attr("x2", function (d) { return updatedXScale(d.target.x); })
.attr("y2", function (d) { return updatedYScale(d.target.y); });
};
ticked();
});
//드래그 기능 설정
var drag = d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended);
//드래그 시작시 이벤트 핸들러
function dragstarted(event, d) {
if(currentAlpha)simulation.alpha(currentAlpha);
simulation.restart();
event.sourceEvent.stopPropagation(); //다른 이벤트 전달 중지
d.fx = null;
d.fy = null;
}
//드래그 중 이벤트 핸들러
function dragged(event, d) {
var mouse = d3.pointer(event, svg.node());
d.x = xScale.invert(mouse[0]);
d.y = yScale.invert(mouse[1]);
// 앞에서 임시로 fix해준 점의 변위 (px, py)를 직접 입력해 줌
d.px = d.x;
d.py = d.y;
simulation.alpha(1).restart();
}
//드래그 종료 이벤트 핸들러
function dragended(event, d) {
d.fx = null;
d.fy = null;
}
//D3의 forceSimulation 함수를 사용하여 물리 기반 레이아웃 생성
//link는 노드 사이의 거리를, charge는 노드간의 인력/반발력을, center는 노드의 중심 위치를 제어함
var force = d3.forceSimulation()
.force("link", d3.forceLink().distance(30))
.force("charge", d3.forceManyBody().strength(-120))
.force("center", d3.forceCenter(width / 2, height / 2));
//svg 요소 생성 후 줌 기능 추가
var svg = d3.select(ccontentId).append("svg")
.attr("width", width)
.attr("height", height)
.call(zoomer);
//forceSimulation에 노드 데이터를 연결하고, 노드 사이의 링크(연결선)를 생성
force
.nodes(graph.nodes)
.force("link")
.links(graph.links);
//연결선을 svg에 추가하고, 연결선의 두께와 색상 설정
var link = svg.selectAll(".link")
.data(graph.links)
.enter().append("line")
.attr("class", "link")
.style("stroke-width", function(d) {
return Math.sqrt(d.count); // 연결선의 두께는 관계의 강도를 나타내는 d.count의 제곱근에 비례함
})
.style("stroke", "#e0e0e0"); // 연결선의 색상을 검은색으로 설정
//노드를 svg에 추가
var node = svg.selectAll(".node")
.data(graph.nodes)
.enter().append("g")
.attr("class", "node")
//노드의 반지름 크기를 결정하는 스케일 설정
var radiusScale = d3.scaleLinear()
.domain([d3.min(graph.nodes, d => d.count), d3.max(graph.nodes, d => d.count)]) // 입력 범위: 노드의 최소와 최대 count 값
.range([5, 30]); // 출력 범위: 최소와 최대 반지름 크기
//노드의 반지름과 색상 설정, 드래그와 마우스 오버 이벤트 추가
node.append("circle")
.attr("r", function(d) { return radiusScale(d.count); }) // 스케일을 적용하여 노드의 반지름 설정
.style("fill", function(d) {
d.groupColor = color(d.group)
return d.groupColor;
}) // 노드의 색상 설정
.call(drag)
.on("mouseover", function(event,d) {
// 마우스 오버 시 툴팁을 표시함
tooltip.transition()
.duration(200)
.style("opacity", .9);
tooltip.html(getTooltipText(d))
.style("left", (event.pageX) + "px")
.style("top", (event.pageY - 28) + "px");
})
.on("mouseout", function(d) {
// 마우스 아웃 시 툴팁을 숨김
tooltip.transition()
.duration(500)
.style("opacity", 0);
});
//툴팁 생성
var tooltip = d3.select("body").select(ccontentId).append("div")
.attr("class", "wordmaptooltip")
.style("opacity", 0);
//툴팁에 표시될 텍스트 설정
function getTooltipText(d) {
return d.tooltip;
}
node.append("text")
.attr("dx", 0)
.attr("dy", function(d) {
return d.rounds > 500 ? "0em" : "-1em";
})
.attr("text-anchor", "middle")
.text(function(d) { return d.name })
.style("font-size", "8px")
//틱 이벤트 핸들러 - 노드와 링크의 위치를 업데이트함
var ticked = () => {
node.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
link.attr("x1", function (d) { return xScale(d.source.x); })
.attr("y1", function (d) { return yScale(d.source.y); })
.attr("x2", function (d) { return xScale(d.target.x); })
.attr("y2", function (d) { return yScale(d.target.y); });
}
//forceSimulation 설정 - 노드를 서로 밀어내며 중앙으로 모으고, 노드 사이의 링크를 추가함
var simulation = d3.forceSimulation(graph.nodes)
.force("charge", d3.forceManyBody().strength(-100))
.force("center", d3.forceCenter(width / 2, height / 2))
.force("link", d3.forceLink(graph.links).distance(200))
.on("tick", ticked);
//범례 생성
GenerateLegend(10,4)(GenerateLegendNode(graph.nodes,MaxSizeLegend),svg);
}
//legend 생성
let GenerateLegend = (legendRectSize, legendSpacing) => (data, svg) => {
// 범례의 색상 척도를 설정합니다.
//var color = d3.scaleOrdinal(colorArray);
let legendPadding = 15; // 범례에 패딩을 추가하려면 이 값을 조정하면 됩니다.
var legend = svg.append("g") // SVG 요소에 범례를 추가합니다.
.attr("class", "legend");
var legendHeight = data.length * (legendRectSize + legendSpacing); // 범례의 높이를 계산합니다.
// 범례의 제목을 추가합니다. 초기에는 빈 문자열입니다.
var legendTitle = legend.append("text")
.attr("class", "legend-title")
.text(" ")
.attr("x", 0)
.attr("y", 0);
// 범례 제목의 너비와 높이를 계산합니다.
var legendTitleWidth = legendTitle.node().getBBox().width;
var legendTitleHeight = legendTitle.node().getBBox().height;
var legendTitleX = legendRectSize + legendSpacing;
var legendTitleY = (legendRectSize / 2) + (legendTitleHeight / 2);
// 범례 제목의 위치를 설정합니다.
legendTitle.attr("x", legendTitleX)
.attr("y", legendTitleY);
// 범례의 각 항목을 생성하고, 위치를 설정합니다.
var legendItem = legend.selectAll(".legend-item")
.data(data)
.enter().append("g")
.attr("class", "legend-item")
.attr("transform", function(d, i) { return "translate(0," + (i * (legendRectSize + legendSpacing) + legendTitleHeight + legendPadding) + ")"; });
// 범례 항목에 원을 추가하고, 색상을 설정합니다.
legendItem.append("circle")
.attr("r", legendRectSize/2)
.style("fill", function(d, i) {
return d.Color;
})
// 원에 마우스 오버 이벤트를 추가합니다. 원을 클릭하면 해당 그룹의 노드와 링크만 표시됩니다.
.on("click", (d, i) => {
console.log('mouseover',this.graph)
LegendSetNodes(svg,LegendGetNodes(i)); //클릭 한 노드 외의 노드들의 투명도를 조절합니다.
LegendSetLines(svg,LegendGetNodes(i)); //클릭 한 노드와 관련 있는 링크들만 보여주게 수정
legend.selectAll(".legend-item").style("opacity", 0.2); // 모든 범례 항목의 투명도를 0.2로 설정합니다.
$(d.srcElement.parentElement).css("opacity", 1); // 클릭된 범례 항목의 투명도는 1로 설정합니다.
})
.on("mouseout", function (d, i) {
// 마우스가 범례 항목에서 벗어났을 때의 동작을 정의합니다.
});
// 범례 항목에 텍스트를 추가합니다.
legendItem.append("text")
.attr("x", legendRectSize + legendSpacing)
.attr("y", legendRectSize / 2)
.attr("dy", "-1px") // 텍스트를 원의 중앙에 더 가깝게 배치합니다.
.style("font-size", '11px')
.on("click", (d, i) => {
console.log('mouseover',d,i)
LegendSetNodes(svg,LegendGetNodes(i));
LegendSetLines(svg,LegendGetNodes(i));
legend.selectAll(".legend-item").style("opacity", 0.2);
$(d.srcElement.parentElement).css("opacity", 1);
})
.text(function(d) { return d.data; }); // 범례 항목의 텍스트는 데이터의 값을 사용합니다.
// 범례의 너비를 계산합니다.
var legendWidth = legend.node().getBBox().width;
// SVG 요소의 너비와 높이를 얻습니다.
var svgWidth = parseInt(svg.attr("width"), 10);
var svgHeight = parseInt(svg.attr("height"), 10);
// 범례의 위치를 설정합니다.
var legendX = svgWidth - legendWidth - legendSpacing;
var legendY = legendSpacing;
legend.attr("transform", "translate(" + legendX + "," + legendY + ")");
// SVG 요소가 클릭되면 범례의 선택 효과를 초기화합니다.
svg.on("click", (event) => {
// 클릭한 요소가 범례의 일부인지 확인합니다.
let target = d3.select(event.target);
if (!(target.node().nodeName === "text" || target.node().nodeName === "circle")) {
console.log('eventevent',target.node())
LegendSetNodes(svg, this.graph.nodes);
svg.selectAll(".link").style("opacity", 1);
legend.selectAll(".legend-item").style("opacity", 1);
}
});
};