💻 다아아아 기록 개인 사이트 이동

IT SW 개발

[Highcharts] 하이차트 Ctrl+C, Ctrl+V로 문서에 복사 붙여넣기_(1)

다아아아 2026. 6. 17. 11:03

 

옛날 옛적 Chat GPT가 없던 시절...

 

첫 회사 생활을 시작한 나는 입사와 동시에 웹사이트 프로젝트에 투입되었다.

프로젝트는 웹사이트에서 차트를 이용해 각종 정보를 보여주는 시스템이였다.

여러 차트 라이브러리 중 하이차트를 사용하기로( = 사용하라고) 하였다.

 

프로젝트가 마무리되어 가던 어느 날.

웹에서 Ctrl+C로 차트를 복사한 뒤 한글, 워드, 엑셀 같은 문서에 Ctrl+V로 붙여넣기 할 수 있게 해달라는 요구사항이 있었다.

지금이야 GPT에게 만들어줘 한마디 하면 샘플 코드가 몇 초 만에 나오겠지만

그 당시 개발자 3개월 아주 초보 개발자 + 웹 처음으로 당황했건 기억이있다.

폭풍 구글링을 통해 만들었던... 

 

예전에 한 번 블로그에 정리했던 내용인데

오랜만에 다시 보니 추억도 떠오르고 지금 기준으로는 꽤 간단한 기능이라 다시 기록해 보기로 했다.

요즘은 라이브러리도 많고 브라우저 API도 좋아져서 더 쉽고 깔끔한 방법이 있을 것이다.

 

동작하는 순서는 이렇다.

웹에서 하이차트를 클릭 → Ctrl+C → 문서(한글, 워드, 엑셀 등)에서 Ctrl+V로 붙여넣기

 

예전 소스는 이렇다.

<head>
	<title>Copy Highcharts to Clipboard Test</title>

	<!--차트 구역 경계선 스타일 지정-->
	<style>
		.chartContainer {
			border-color: rgba(0, 0, 0, .0);
			border-style: solid;
			border-width: 2px;
			border-radius: 2px;
		}
	</style>
	<!-- highchart 사용을 위한 CDN-->
	<script src="https://code.jquery.com/jquery-3.0.0.min.js"></script>
	<script src="https://unpkg.com/highcharts/highcharts.js"></script>
	<script src="https://unpkg.com/highcharts/modules/exporting.js"></script>

</head>

<body id="body">
	<!-- 차트 들어가는 div구역 -->
	<div class="chartContainer" id="container"></div>
	<!-- 셀렉트 박스 들어가는 구역 -->
	<div style="text-align : center ">
		<select id="ChType">
			<option value="column">column</option>
			<option value="pie"> pie</option>
			<option value="line">line</option>
		</select>
	</div>
	<!-- 차트에서 추출한 이미지 들어가는 구역. 높이 1px, 투명도 .0으로 설정하여 숨김 -->
	<div id="imgContainer" class="copyable" style="height:1px; opacity: 0.0;"></div>
</body>

<script>

	//X축에 들어갈 배열
	var albumName = ['2Cool4School', 'O!RUL8,2', 'School Love Affair', 'Dark&Wild', '화양연화 pt.1',
		'화양연화 pt.2', '화양연화 Young Forever', 'WINGS', 'WINGS 외전', 'LoveYourslef'];
	//시리즈 데이터에들어갈 배열
	var albumNum = [101637, 118170, 191444, 199090, 353063, 432740, 440367, 827947, 749954, 1376915];
	//셀렉트의 첫번째 옵션값으로 차트의 초기 타입 설정
	var newType = ChType.options[0].value;
	//차트 생성 함수 호출
	chartCreate();

	//차트구역에 클릭이벤트 발생 시 경계선 스타일변경과 복사를 수행
	$(".chartContainer").click(function (e) {
		//차트 구역에 클릭이벤트 발생 시 경계선 표시
		$('.chartContainer').css('border-color', '#BDBDBD');
		//전체 body에 키 이벤트 발생 시 수행
		document.getElementById("body").onkeydown = function () {
			//발생한 키 이벤트가 ctrl+c일 경우 
			if ((event.keyCode == 67) && (event.ctrlKey == true)) {
				//다시 경계선을 숨김
				$('.chartContainer').css('border-color', 'rgba(0, 0, 0, .0)');
				//셀렉션 객체 생성
				var selection = window.getSelection();
				//레인지 객체 생성
				var range = document.createRange();
				//레인지가 copyable 노드를 포함
				range.selectNodeContents($(".copyable").get(0));
				//레인지를 삭제
				selection.removeAllRanges();
				//셀렉션에 레인지를 넣어줌
				selection.addRange(range);
				//클립보드에 현재 내용을 복사
				document.execCommand('copy');
				//셀렉션에서 모든 레인지를 삭제
				window.getSelection().removeAllRanges();
			}
		}
	});

	//셀렉트 박스 변경시 수행하는 함수
	$('#ChType').change(function () {
		//changeType함수에 인자로넘겨줄 series에 시리즈를 넣어준다.
		var series = chart.series[0];
		//셀렉트 박스에서 선택된 값을  newType에 넣어준다.  
		newType = jQuery('#ChType option:selected').val();
		//시리즈,타입 변경 함수 호출
		changeType(series, newType);
		//셀렉트 박스 변경 시 경계선을 투명하게 되돌림
		$('.chartContainer').css('border-color', 'rgba(0, 0, 0, .0)');
	})

	//차트 옵션 변경 함수
	function changeType(series, newType) {
		//로드 이후에 차트에 시리즈 변경
		series.chart.addSeries({
			type: newType,
			name: series.name,
			data: series.options.data
		}, false);
		//기존의 시리즈 내용 삭제
		series.remove();
		//기존에 있던 추출된 이미지 삭제
		$("#copyimage").remove();
		//다시 차트를 생성하기위한 obj 비우기
		obj = null;
		//함수 생성 함수 호출
		chartCreate();
	}

	//차트 생성 함수
	function chartCreate() {
		options = {
			//차트 타입
			chart: {
				type: newType
			},
			//우측 하단 로고
			credits: {
				text: 'MTFIS',
			},
			//차트 타이틀
			title: {
				text: 'BTS Album Sale Rate',
				//스타일 두껍게
				style: {
					fontWeight: 'bold'
				}
			},
			//색상 설정
			colors: [
				'#B40404'
			],
			//서브 타이틀 설정
			subtitle: {
				text: 'BTS(bangtanboys) total sales trend album by sale rate.'
			},
			//Y축 타이틀 설정
			yAxis: {
				title: {
					text: '앨범 발매 일순 / 단위 : 장'
				}
			},
			//X축에 들어갈 배열
			xAxis: {
				categories: albumName
			},
			series: [{
				data: albumNum,
				name: 'Sale rate'
			}],
			//exporting시 파일이름, exporting.url 설정
			exporting: {
				filename: 'BTS Album Sale Rate',
				url: 'http://export.highcharts.com/'
			}
		},
			chart = Highcharts.chart('container', options);

		//ajax통신에 사용할 테이터 생성
		obj = {},
			//exportUrl을 차트 옵션의 exporting.url을 넣어준다.
			exportUrl = options.exporting.url;
		//옵션을 보내고 문자열 형태로 받아오기 위해 JSON.stringify사용 
		obj.options = JSON.stringify(options);
		//이미지 파일은 png 
		obj.type = 'image/png';
		//동기 통신으로 설정. 데이터의 결과를 수신 받은 다음 진행 함
		obj.async = true;

		$.ajax({
			//전송 방식
			type: 'post',
			//전송 url
			url: exportUrl,
			//전송할 데이터
			data: obj,
			//전송 성공시 함수 수행
			success: function (data) {
				//imgContainer는 위 이미지가 들어갈 구역의 id 저장
				var imgContainer = $("#imgContainer");
				//이미지에 태그 설정
				$('<img>').attr('src', exportUrl + data).attr('width', '400px')
					.attr('id', 'copyimage')
					.attr('style', 'height:1px; opacity: 0.0;')
					//<img src="http://export.highcharts.com/+data" 
					//width="400px" id="copyimage" style="height:1px; opacity: 0.0;">
					//위의 이미지태그가 imgContainer에 들어가 이미지가 생성된다.
					.appendTo(imgContainer);
			}
		});
	}
</script>

당시에는 블로그 글을 작성하면서 한 줄 한 줄 주석을 엄청 자세하게 달아놨기 때문에 지금 봐도 대충 어떤 의도로 작성했는지 이해가 될것이다.

지금은 이 소스가 동작하지 않는다.

너무 오래된 방식이기 때문이다.