d3.js 로 forcedirectlyGraph만들기

정태민·2023년 6월 14일
0

회사업무

목록 보기
17/17

//트리 생성 함수
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); 
		}
	});
};
profile
퇴근후 30분 출근전 30분

0개의 댓글