D3를 이용하여 svg에 데이터를 시각화 하고나면 그 결과를 공유해야 할 일이 생길 때가 있다. 가장 간단한 방법은 웹 사이트의 링크를 공유하는 것이지만, 때에 따라 문서에 첨부하거나 파일로 저장하여 공유해야 할 수도 있다. 보통 이러한 경우 캡처 프로그램을 사용하면 아주 간단하게 원하는 작업을 수행할 수 있으나, 이미지의 크기가 한 화면에 들어오지 않거나 저장해야 하는 이미지의 개수가 많으면 캡처 프로그램으로만으로는 한계가 있다. 따라서 이번 포스트에서는 d3로 만들어낸 svg 형태의 image를 우리에게 익숙한 png image로 출력하여 저장하는 방법을 정리하고자 한다.

이 작업을 위해서 구글링을 해보니 아래와 같은 링크를 발견할 수 있었다.

http://techslides.com/save-svg-as-an-image

  svg를 png로 export 하는 방법에 대해 아주 잘 정리 되어 있었다. 그러나 위의 링크 만으로 모든게 해결 되었다면 이 post를 작성하지 않았을 것이다. 위의 링크에 제시된 예제 코드를 그대로 실행하면 아무런 오류 없이 원하는 결과를 얻을 수 있었다. 그래서 작업 중이었던 코드에 삽입해서 image로 추출하는 것을 시도해보았으나 에러가 발생했다. 이럴 때마다 느끼는 거지만 정말 코딩 할 때 딱 한번에 잘 되는 경우가 잘 없다. 에러는 파이썬에서도 날 정말 많이 괴롭혔던 '인코딩'이 원인이었다. 아래의 코드에서 8번 째 줄이 에러를 발생 시키는 부분이다


  btoa 함수는 아마 binary to ascii 의 약자로 추정되며 svg 데이터를 base64로 encoding 해주는 함수이다. 웹 브라우저에서 image는 'base64'로 인코딩된 포멧이기 때문에 data를 전송하기 위해서 binary data를 base64로 인코딩을 해줘야 한다. (출처) 그런데 btoa 함수에서 에러가 발생하는 것이다. 에러 메세지를 구글에서 검색해보니 인코딩 문제라고 한다. 예제 코드에는 없지만 내 코드에는 있는 것. 바로 한글의 존재 때문에 에러가 발생하는 것이다. 

  btoa 에 인자로 들어가는 html은 script 부분에 작성된 모든 코드를 통째로 긁어온 string 헝태인데 (), 여기에 한글이 들어가면 btoa는 utf-8 형식을 인식하지 못하기 때문에 에러가 나는 것이다. 그래서 저 부분을 해결하기 위해 stackoverflow에서 발견한 페이지에서 하라는 함수를 만들어서 전처리를 해보았다. 에러는 발생하지 않았으나 여러번 변환이 된 탓인지 뒤 쪽 코드의 그림을 그리는 부분에서 아무런 결과를 산출하지 못했다. (이 부분은 조만간 다시 한 번 시도해 보려고 한다.) 그래서 다른 방법을 찾던 중 utf-8 을 그대로 이용하는 코드를 발견했다. 이 코드는 image를 단순히 같은 화면에 복제 시킨 후 우클릭으로 그림으로 저장하는 방식으로 image를 추출하고 있었다. 그래서 위의 링크에 나온 방법과 이 코드의 앞 부분을 섞어서 드디어 원하는 결과를 얻을 수 있었다. 코드는 다음과 같다.


  Blob 이라는 자료형을 사용하는 방법이다. Blob에 대해서는 정확히 모르겠지만 utf-8 로 주소를 편하게 읽어올 수 있다는 것이 장점인 듯 하다. url을 utf-8 형식 그대로 읽고 나서 그 후의 방식은 가장 위에 첨부했던 링크에 나와 있는 방법과 거의 흡사하다. script에 이 code를 작성하기 전에 html의 body 부분에 두 개의 준비물이 필요한데 하나는 일반적인 <div> 이고 나머지 하나는 <canvas>이다. 코드의 진행과정은 다음과 같다. button을 클릭하면 svg를 미리 만들어둔 <div>에 image로 변환해 놓고 <canvas>에서 이 image를 읽어와서 파일의 형태로 만들어서 저장할 수 있게 한다. 이 때, <div>의 style을 display: none으로 해주어야 화면에는 아무런 변화가 없이 깔끔하게 추출만 할 수 있다.

마지막 37번 째 줄처럼 context를 clear해주지 않으면 데이터 변화에 따른 시각화의 변화가 갱신되지 않고 누적되기 때문에 꼭 저렇게 clear를 해주어야 한다.

인코딩 문제를 해결하고 그림을 추출하는 것까지는 성공했으나.. style sheet 정보까지 image에 포함시켜 추출시키는 것은 여전히 모르겠다. 즉, script 안에서 여러 가지 method로 선언해준 것들은 image에 포함이 되나 style 부분에서 지정해준 것은 전혀 반영이 되지 않는다. 이 문제까지 해결하면 svg의 image 파일 추출은 거의 온전하게 할 수 있을 것 같은데 쉽지가 않다.



D3를 이용하는 목적은 여러가지가 있겠지만 아무래도 interactive 한 시각화가 요구 될 때 시각화 도구로 D3를 많이들 사용하게 된다. 특히 특정 버튼을 눌렀을 때 내부의 데이터가 바뀌면서 화면도 시시각각 바뀌는 것이 그 대표적인 예라고 할 수 있다. 이 때, 이러한 조작을 가능하게 해주는 것이 D3의 enter, update, exit 개념이다. 이 개념은 D3의 핵심 개념으로 화려하고 다양한 D3의 여러가지 기능을 쫓다보면 놓치기 쉽다. 나 역시 이 개념을 간과하고 신나게 코딩하다가 D3정말 D3답지 않게 코딩했다가 코드를 전면 수정한 적이 있다. 혹여 D3를 시작하는 사람이라면 이 개념을 꼭 짚고 넘어가는 것이 좋다. 이 개념에 대한 설명은 다양한 웹문서에 잘 나와 있으니 참고하길 바란다.

어쨌든, 이 개념을 이용해서 코딩을 하려고 하는데 간혹 'exit is not a function' 이라는 에러 메세지를 마주 하게 되었다. 아래 코드는 이런 에러메세지를 발생 시키는 코드 예제이다.


이 코드가 포함된 동적 시각화를 웹 페이지에서 조작해보면 exit() 함수 부분에서 에러가 난다. Stackoverflow를 찾아보니 enter()에 바로 이어서 append로 어떤 객체를 붙이면, barElements는 enter() 로 subselection된 노드가 되고 이 노드들은 정의에 따라 exit() 함수를 가질 수 없다고 한다. (http://stackoverflow.com/questions/27596908/d3-typeerror-link-exit-is-not-a-function-link-exit-remove-what-am-i-doing)

코드를 고쳐서 바로 작성하면 다음과 같다.

올바른 코드는 enter()와 append()를 data()함수에 계속 이어서 하지 말고 위의 코드에서 처럼 한 번 끊어서 해주는 것이다. 이렇게 해주면 전체 data-bound selection을 먼저 한 후 sub-selection에 대해 작업을 하게 되는 코드가 된다고 한다. 솔직히 원리는 완벽하게 이해가 되지는 않지만 코드 상으로 뚜렷한 차이가 보이기 때문에 다음부터는 확실히 구분지어 해야겠다는 생각이 든다. 여튼 알면 알수록 신기하고 헷갈리기도 하는 D3의 enter, update, exit 개념이다.


예전에 연구실 선배에게 논문에 포함될 heatmap을 Matlab으로 그려보는 것이 어떻겠냐는 제안을 받았었다. Matlab에는 heatmap을 그리기 위한 라이브러리가 포함되어 있었는데 당시에 그 라이브러리를 사용해서 heatmap을 그려보려 했는데 의도한대로 잘 되지 않았던 경험이 있었다. 작년 하반기에 d3를 공부하면서 d3로 heatmap을 제대로 그려보고자 했다. 그런데 당연히 있을 것이라 생각했던 heatmap 라이브러리가 d3에는 없었다.

아래는 2014년 방영되었던 KBS 대하 드라마 정도전의 데이터를 이용하여 heatmap을 시각화 해본 것이다. 주인공 정도전이 50회 동안 총 만난 횟수가 10번 이상이 되는 등장인물들만 나타냈다. (등장인물의 수가 많아서 모바일로 보면 몇 명과의 관계까지만 볼 수 있음)

<javascript 코드 추가예정..>

Raw data는 다음과 같이 구성되어 있다.

ep char value
1 이지란 0
1 남은 0
1 업둥이 0
1 이숭인 1
.

.
2 이지란 0
2 남은 0
2 업둥이 0
2 이숭인 0
2 이방과 0
.

.

차례로 회차, 등장인물 이름, 정도전과 만난 횟수를 각각 의미한다.
이 데이터를 chart 영역에 입력하고 heatmap을 그리는 핵심코드는 다음과 같다.



여기서 chas는 heatmap에 나타나는 등장인물들을 raw data의 순서에 맞게 저장해 놓은 array이다. 우선 사각형을 회차와 각 등장인물에 맞게 메트릭스 형태로 잘 그려준다. 하나의 행은 하나의 회차에 대응되고 하나의 열은 한 명의 등장인물에 대응된다. (attr('x') , attr('y') 부분이 표현하고자 하는 heatmap에 적당하게 사각형들의 위치를 설정하는 부분이다.)

그리고 약간의 애니메이션 효과를 위해 처음에는 가장 기본색으로 색칠을 한 후 1초 후에 미리 정해놓은 colorScale에 맞추어 색칠을 해주면 별다른 라이브러리 없이 d3의 기본적인 기능만으로 heatmap을 그릴 수가 있다.

+ Recent posts