React에서 D3.js 사용하는 방법

React에서 D3.js 사용하는 방법에 대해 알아봅니다.

작성자

조덕기

조덕기

DanielCho

본 포스팅은 Christoph MichelHow to use D3.js in React 를 저자의 허락하에 번역한 글입니다. 오탈자, 오역 등이 있다면 연락부탁드립니다.

React와 D3.js를 함께 사용할 때의 문제점은 두 라이브러리 모두 DOM의 렌더링을 장악/통제하려 한다는 것이다. D3.js 는 선택과 하위 항목의 첨부 + 업데이트를 통해 DOM을 수정하고, React는 요소의 성질이나 상태가 변할 때마다 렌더 함수의 렌더링 요소를 통해 DOM을 수정한다. 이 접근법들을 보고 그 방법들을 결합시킬 방법도 살펴 보자.

React를 사용하여 렌더링하기

React는 JSX에서 직접 svg 요소 렌더링을 처리할 수 있어서 우리는 D3의 렌더링 능력을 전혀 이용할 필요가 없다. 차라리 D3를 데이터의 쉬운 로딩, 활용, 포맷팅을 가능하게 하는 d3-scaled3-requestd3-path 등과 같은 D3 도움 함수로 사용할 것이다. 그 후에 우리는 렌더링을 처리하는 React 컴포넌트를 만들 수 있다.

d3.csv('data.csv', (err, data) => {
    if (err) {
        console.log(err)
        return
    }
    ReactDOM.render(
        <App width={960} height={640} data={data} />,
        document.getElementById('root'),
    )
})

function App({width, height, data}) {
    return (
        <svg width={width} height={height}>
            <Bubbles data={data} />
        </svg>
    )
}

function Bubbles({data}) {
    const bubbles = data.map( ({id,x,y,r}) => <Bubble key={id} x={x} y={y} r={r} />)
    return (
        <g className="bubbles">
            {
                bubbles
            }
        </g>
    )
}

function Bubble({x,y,r}) {
    return (
        <circle cx={x} cy={y} r={r} />
    )
}

장점

  • 더 나은 차트 구조
  • 더 읽기 쉬움

단점

  • DOM을 직접 수정할 수 있는 d3.transition과 기타 D3 함수를 사용하지 않음
  • 요소의 소품과 재 렌더링에 애니메이션 효과를 주는 것이 D3의 애니메이션을 사용하는 것보다 느림 (React와 순수 D3의 비교 참조)

D3사용하여 렌더링하기

또 다른 옵션은 React의 렌더링을 다음과 같이 사용하지 못하게 한다. shouldComponentUpdate() { return false }

그리고 D3가 svg elements를 만들도록 하는 것이다. 우리는 단일 svg 컨테이너를 렌더링하는 하나의 React 컴포넌트를 만들고, constructor (props)componentWillReceiveProps (nextProps) 함수에서 주어진 데이터 변화에 따라 D3가 DOM을 만드는 것을 처리하도록 한다. 애니메이션은 주로 외부 이벤트에 의해 작동된다. 예를 들어, 버튼 클릭은 React 컴포넌트 트리를 통해 전파되고, D3 요소의 componentWillReceiveProps를 불러내고, 그것은 D3의 업데이트 선택을 사용하여 트렌지션 효과를 작동시킨다.

export class App extends React.Component {
    state = {
        g: null,
    }

    onRef = (ref) => {
        this.setState({ g: d3.select(ref) }, () => this.renderBubbles(this.props.data))
    }

    renderBubbles(data) {
        const bubbles = this.state.g.selectAll('.bubble').data(data, d => d.id)

        // Exit
        bubbles.exit().remove()

        // Enter
        const bubblesE = bubbles.enter().append('circle')
            .classed('bubble', true)
            .attr('r', 0)
            .attr('cx', d => d.x)
            .attr('cy', d => d.y)

        // Update
        // ...

        // can use animations like this now
        bubblesE.transition().duration(2000).attr('r', d => d.radius)
    }

    componentWillReceiveProps(nextProps) {
        // we have to handle the DOM ourselves now
        if (nextProps.data !== this.props.data) {
            this.renderBubbles(nextProps.data)
        }
    }

    shouldComponentUpdate() { return false }

    render() {
        const { width, height } = this.props
        return (
            <svg width={width} height={height}>
                <g ref={this.onRef} className="bubbles" />
            </svg>
        )
    }
}

장점

  • d3.transition 애니메이션처럼 D3의 모든 함수를 사용할 수 있음

단점

  • 하나의 컴포넌트에 전체 차트를 넣음으로써 구조화가 약함
  • 개별 파트간 Tight-coupling 발생
  • 읽기 힘듦

하이브리드 접근법

나는 하이브리드 접근법을 선호한다. React 컴포넌트를 사용하면서 얻게되는 구조와 가독성을 선호하지만, DOM 요소에서 직접 전환 효과를 사용하는 것 또한 좋아한다. 그래서 필자는 두 가지 접근법을 결합한다. React가 대부분의 모든 고정 요소 (컨테이너, 타이틀, 축, 범례) 를 렌더링 하도록하고 D3가 애니메이션화 해야하는 모든 것들을 (데이터 시리즈) 렌더링 하도록 한다. 예를 들어, 이 방법의 실행을 내 Bubble Chart on GitHub 에서 볼 수 있다 ( 또는 여기에서 실행할 수 있다.)

버블 요소를 제외한 모든 요소는 고정 요소이며, 변화하지 않으므로 필자는 이 요소들을 React 내에서 렌더링한다. 버블 요소는 D3에서 force layout을 사용하여 원형들을 렌더링하고 애니메이션화한다. 그렇게 하면 App.js 는 다음과 같이 보인다.

export default class App extends React.Component {
 state = {
    data: [],
    grouping: 'all',
  }

  onGroupingChanged = (newGrouping) => {
    this.setState({
      grouping: newGrouping,
    })
  }

  render() {
    const { data, grouping } = this.state
    return (
      <div className="App">
        <GroupingPicker onChanged={this.onGroupingChanged} active={grouping} />
        <BubbleChart width={width} height={height}>
          <Bubbles data={data} forceStrength={0.03} center={center} yearCenters={yearCenters} groupByYear={grouping === 'year'} />
          {
            grouping === 'year' &&
            <YearsTitles width={width} yearCenters={yearCenters} />
          }
        </BubbleChart>
      </div>
    )
  }
}

장점

  • 좋은 독립적 차트 구조
  • 적절한 곳(애니메이션)에 D3를 직접적으로 사용할 수 있음

결론

React에서 어떻게 D3를 사용하는 지에 대한 좋은 글들이 많다. React나 D3 둘 중 하나가 모든 렌더링을 하도록 하는 방법들에 대해서 말이다. 필자는 React가 차트의 모든 고정된 부분을 렌더링하도록 하고, 친숙한 d3.transition 으로 시각화의 동적 부분을 처리하는데 D3의 기능을 사용하는 하이브리드 접근법이 가장 사용하기에 좋다고 생각한다.

Tags : javascript D3 

comments powered by Disqus