1. 개요

이전 글에서는 라인차트의 기본 기능을 구현해보았다.

하지만 해당 차트는 데이터를 받아서 그릴 수만 있을 뿐, 세부적인 설정을 할 수 없었다. 이 글에서는 세부적은 설정을 할 수 있도록 옵션 기능을 구현할 것이다.

2. 구현

이전 글에서 우리는 차트를 크게 네 개의 컴포넌트로 나누어 구현했다.

comp/LineChart/LineChartBody/index.tsx
function LineChartBody(/* ... */) {
  return (
    <Fragment>
      <XAxis {/* ... */} />
      <YAxis {/* ... */} />
      <Grid {/* ... */} />
      <Lines {/* ... */} />
    </Fragment>
  );
}

그러니 옵션을 구현하는 것도 각 컴포넌트 별로 초점을 맞춰서 진행해보자.

2.1. <XAxis /> 옵션 구현

X 축을 그릴 때 선택적으로 설정할 수 있는 것들은 무엇이 있을까? 하고 생각해보면 아래와 같은 요소들이 떠오른다.

  • X 축 자체를 보이게/안보이게 하는 기능
  • X 축 라인의 색상, 두께
  • 틱을 보이게/안보이게 하는 기능
  • 틱으로 사용될 값들의 목록
  • 틱 선의 길이, 색상, 두께
  • 틱 라벨의 폰트, 폰트 크기, 폰트 두께, 폰트 색상, 포메터(formatter)

이외에도 많은 요소들이 있을 수 있다. 하지만 이번 글에서는 일단 이 정도만 구현하자.

2.1.1. 옵션 타입 추가

위에서 나열한 요소들을 타입으로 작성하자.

comp/LineChart/types.ts
import { ScaleTime } from "d3";
import { FontWeight } from "react-native-svg";

export type XAxisOptions = {
  enabled?: boolean;

  // X 축의 위치 offset
  x?: number;
  // X 축의 위치 offset
  y?: number;

  // X 축 라인 색상
  lineColor?: string;
  // X 축 라인 두께
  lineWidth?: number;

  // 틱을 보여줄지/안보여줄지 여부
  showTicks?: boolean;
  // 틱으로 사용될 값 혹은 값을 반환하는 함수
  ticks?: Date[] | ((scale: ScaleTime<number, number, never>) => Date[]);

  // 틱의 길이
  tickLength?: number;
  // 틱의 두께
  tickWidth?: number;
  // 틱의 색상
  tickColor?: string;

  // 틱 라벨의 폰트 크기
  tickLabelSize?: number;
  // 틱 라벨의 폰트
  tickLabelFont?: string;
  // 틱 라벨의 폰트 두께
  tickLabelWeight?: FontWeight;
  // 틱 라벨의 폰트 색상
  tickLabelColor?: string;
  // 틱 라벨의 형식 (값을 받아 틱 라벨을 반환하는 함수)
  tickLabelFormatter?: (value: Date) => string;
};

2.1.2. <LineChart />로부터 옵션 전달

좋다. 이제 이 값들을 <LineChart /> 가 받을 수 있도록 한 뒤에, 받은 값을 <XAxis /> 까지 건내주자.

comp/LineChart/index.tsx
// ...
import { XAxisOptions } from "./types";

// ...

type LineChartProps = {
  // ...
  xAxisOptions?: XAxisOptions;
};
function LineChart({
  // ...
  xAxisOptions,
}: LineChartProps) {
  // ...

  return (
    <Svg {/* ... */}>
      {loaded && (
        <LineChartBody
          // ...
          xAxisOptions={xAxisOptions}
        />
      )}
    </Svg>
  );
}

export default LineChart;
comp/LineChart/LineChartBody/index.tsx
// ...
import { XAxisOptions } from "../types";

// ...
type LineChartBodyProps = {
  // ...
  xAxisOptions?: XAxisOptions;
};
function LineChartBody({
  // ...
  xAxisOptions,
}: LineChartBodyProps) {
  return (
    <Fragment>
      <XAxis scale={xScale} paneBoundary={paneBoundary} {...xAxisOptions} />
      <YAxis {/* ... */} />
      <Grid {/* ... */} />
      <Lines {/* ... */} />
    </Fragment>
  );
}

export default LineChartBody;

2.1.3. <XAxis /> 수정

옵션을 전달했으니, <XAxis /> 에서 해당 옵션을 사용하도록 구현을 수정해보자.

comp/LineChart/LineChartBody/XAxis.tsx
// ...

import { XAxisOptions } from "../types";

const DEFAULT_TICK_LENGTH = 6;

// `XAxisOptions` 타입의 속성을 모두 prop 으로 받을 수 있도록 지정해주자.
type XAxisProps = XAxisOptions & {
  scale: d3.ScaleTime<number, number, never>;
  paneBoundary: PaneBoundary;
};
function XAxis({
  scale,
  paneBoundary,
  x = 0,
  y = paneBoundary.y1,

  enabled = true,

  lineColor = "black",
  lineWidth = 1,

  showTicks = true,
  ticks: _ticks,

  tickLength = DEFAULT_TICK_LENGTH,
  tickWidth = 1,
  tickColor = "black",

  tickLabelSize,
  tickLabelFont,
  tickLabelWeight,
  tickLabelColor = "black",
  tickLabelFormatter = dateFormat,
}: XAxisProps) {
  const range = scale.range();
  const ticks = !_ticks
    ? scale.ticks()
    : typeof _ticks === "function"
    ? _ticks(scale)
    : _ticks;

  if (!enabled) {
    return null;
  }

  return (
    <G>
      <Line
        x1={range[0] + x}
        x2={range[1] + x}
        y1={y}
        y2={y}
        stroke={lineColor}
        strokeWidth={lineWidth}
      />

      {showTicks && (
        <>
          {ticks.map((tick) => (
            <Line
              key={`${tick}`}
              x1={scale(tick)}
              x2={scale(tick)}
              y1={y}
              y2={y + tickLength}
              stroke={tickColor}
              strokeWidth={tickWidth}
            />
          ))}
          {ticks.map((tick) => (
            <Text
              key={`${tick}`}
              x={scale(tick)}
              y={y + tickLength + 2}
              fill={tickLabelColor}
              fontSize={tickLabelSize}
              fontFamily={tickLabelFont}
              fontWeight={tickLabelWeight}
              textAnchor="middle"
              alignmentBaseline="hanging"
            >
              {tickLabelFormatter(tick)}
            </Text>
          ))}
        </>
      )}
    </G>
  );
}

export default XAxis;

이제는 이전 글의 코드와 달리 몇몇 소수의 값을 제외하고는 하드코딩이 사라진 것을 확인할 수 있다.

만약 여전히 하드코딩되어있는 값들 (textAnchor, alignmentBaseline) 도 옵션으로 지정하고 싶다면 XAxisOptions 타입에 해당하는 값을 추가한 뒤 똑같이 구현하면 된다.

2.1.4. <LineChart /> 사용 시 옵션 적용 예

이제 X 축의 속성들을 <LineChart /> 컴포넌트 사용자가 직접 지정할 수 있게 되었다.

<LineChart
  series={dummySeries}
  width="100%"
  height={200}
  xAxisOptions={{
    enabled: true,
    lineColor: "red",
    lineWidth: 2,
    tickLength: 15,
    tickWidth: 3,
    tickColor: "green",
    tickLabelSize: 20,
    tickLabelWeight: 100,
    tickLabelFormatter: (date) => `${date.getMonth() + 1}.${date.getDate()}`,
  }}
/>

2.2. <YAxis /> 옵션 구현

그럼 이제 Y 축도 수정해보자.

Y 축도 X 축처럼 옵션으로 할만한 요소들을 생각해보자.

  • Y 축 자체를 보이게/안보이게 하는 기능
  • Y 축 라인의 색상, 두께
  • 틱을 보이게/안보이게 하는 기능
  • 틱으로 사용될 값들의 목록
  • 틱 선의 길이, 색상, 두께
  • 틱 라벨의 폰트, 폰트 크기, 폰트 두께, 폰트 색상, 포메터(formatter)

나열하고보니 X 축의 옵션과 거의 동일해보인다. 이렇다면 2.1.1. 에서 만든 XAxisOptions와 공통의 상위 타입을 만든 다음에 상속 받는 식으로 만들어도 괜찮을 것 같다.

2.2.1. 옵션 타입 추가 및 수정

comp/LineChart/types.ts
// - `XAxisOptions` 를 `AxisOptions` 로 바꾸고
//  몇몇 타입들을 generic 하게 받을 수 있도록 변경했다
export type AxisOptions<Scale, Value> = {
  enabled?: boolean;

  x?: number;
  y?: number;

  lineColor?: string;
  lineWidth?: number;

  showTicks?: boolean;
  ticks?: Value[] | ((scale: Scale) => Value[]);

  tickLength?: number;
  tickWidth?: number;
  tickColor?: string;

  tickLabelSize?: number;
  tickLabelFont?: string;
  tickLabelWeight?: FontWeight;
  tickLabelColor?: string;
  tickLabelFormatter?: (value: Value) => string;
};
// - `XAixsOptions` 의 역할을 하는 `TimeAxisOptions`
// - 왜 이름이 바뀌었냐면 나중에 상황에 따라 Y 축이 시계열이 될 수도 있겠다는 생각이 들어서
//  유연하게 사용하려는 의도이다.
export type TimeAxisOptions = AxisOptions<
  ScaleTime<number, number, never>,
  Date
>;
// `YAxis` 의 옵션 타입으로 쓰일 `LinearAxisOptions`
export type LinearAxisOptions = AxisOptions<
  ScaleLinear<number, number, never>,
  number
>;

2.2.2. <LineChart />로부터 옵션 전달

<LineChart /> 로부터 <YAxis /> 까지 옵션을 전달하는 것은 2.1.2. 와 거의 동일하다. 2.1.2. 혹은 3.1. 을 참고하자.

2.2.3. <YAxis /> 수정

<YAxis /> 구현 자체도, 2.1.3. 의 <XAxis /> 와 대동소이 하다.

comp/LineChart/LineChartBody/YAxis.tsx
import { LinearAxisOptions } from "../types";

const DEFAULT_TICK_LENGTH = 6;

// `LinearAxisOptions` 타입의 속성을 모두 prop 으로 받을 수 있도록 지정해주자.
type YAxisProps = LinearAxisOptions & {
  scale: d3.ScaleLinear<number, number, never>;
  paneBoundary: PaneBoundary;
};
function YAxis({
  scale,
  paneBoundary,
  x = paneBoundary.x1,
  y = 0,

  enabled = true,

  lineColor = "black",
  lineWidth = 1,

  showTicks = true,
  ticks: _ticks,
  tickLength = DEFAULT_TICK_LENGTH,
  tickWidth = 1,
  tickColor = "black",

  tickLabelSize,
  tickLabelFont,
  tickLabelWeight,
  tickLabelColor = "black",
  tickLabelFormatter = (val) => `${val}`,
}: YAxisProps) {
  const range = scale.range();
  const ticks = !_ticks
    ? scale.ticks()
    : typeof _ticks === "function"
    ? _ticks(scale)
    : _ticks;

  if (!enabled) {
    return null;
  }

  return (
    <G>
      <Line
        x1={x}
        x2={x}
        y1={range[0] + y}
        y2={range[1] + y}
        stroke={lineColor}
        strokeWidth={lineWidth}
      />
      {showTicks && (
        <>
          {ticks.map((tick) => (
            <Line
              key={`${tick}`}
              x1={x - tickLength}
              x2={x}
              y1={scale(tick)}
              y2={scale(tick)}
              stroke={tickColor}
              strokeWidth={tickWidth}
            />
          ))}
          {ticks.map((tick) => (
            <Text
              key={`${tick}`}
              x={x - tickLength - 2}
              y={scale(tick)}
              fill={tickLabelColor}
              fontSize={tickLabelSize}
              fontFamily={tickLabelFont}
              fontWeight={tickLabelWeight}
              textAnchor="end"
              alignmentBaseline="middle"
            >
              {tickLabelFormatter(tick)}
            </Text>
          ))}
        </>
      )}
    </G>
  );
}

export default YAxis;

이제는 이전 글의 코드와 달리 몇몇 소수의 값을 제외하고는 하드코딩이 사라진 것을 확인할 수 있다.

만약 여전히 하드코딩되어있는 값들 (textAnchor, alignmentBaseline) 도 옵션으로 지정하고 싶다면 LinearAxisOptions 타입에 해당하는 값을 추가한 뒤 똑같이 구현하면 된다.

2.2.4. <LineChart /> 사용 시 옵션 적용 예

이제 Y 축의 속성들을 <LineChart /> 컴포넌트 사용자가 직접 지정할 수 있게 되었다.

<LineChart
  series={dummySeries}
  width="100%"
  height={200}
  yAxisOptions={{
    enabled: true,
    lineColor: "brown",
    lineWidth: 3,
    tickLength: 2,
    tickWidth: 3,
    tickColor: "yellow",
    tickLabelSize: 8,
    tickLabelWeight: 700,
    tickLabelFormatter: (val) => val.toLocaleString(),
  }}
/>

2.3. <Grid /> 옵션 구현

<Grid /> 의 옵션을 구현하기 위해 코드를 천천히 살펴보다가 이런 의문이 생겼다.

"지금까지 구현했던 차트들을 떠올려보면 그리드 라인은 보통 별개로 존재하지 않는다. X 축 혹은 Y 축의 틱과 값을 공유한다. 왜냐하면 사용자 입장에서 그리드 라인에 해당하는 값 혹은 날짜가 무엇인지 알고 싶은데, 이것을 따로 표시하기보다는 X,Y 축에 표시하는 것이 일관성 있기 때문이다."

"아니 그 이전에, 그리드라인의 태생이 X,Y 축의 틱의 연장선인게 아닌가? 내가 너무 생각 없이 축과 그리드라인을 분리해서 생각한 것이 아닐까?"

의문을 해결하기 위해, 이럴 때 레퍼런스로 많이 활용하는 highcharts 의 데모API 문서D3.js 의 예제들을 살펴보았다. 살펴 본 결과, 내가 잘못 생각하고 있던 게 맞았다는 걸 깨달았다.

따라서 <Grid> /> 컴포넌트는 삭제 하고, 해당 컴포넌트의 기능을 <XAxis /><YAxis /> 에 편입시켰다. 이제 그리드 라인을 활성화/비활성화하기 위해서는 <Grid /> 컴포넌트를 사용하는 것이 아니라 <XAxis />, <YAxis />의 옵션으로 설정해야 한다.

2.3.1. 옵션 타입 수정

TimeAxisOptions, LinearAxisOptions 의 부모인 AxisOptions 에 그리드 라인 관련 속성을 추가하자.

comp/LineChart/types.ts
export type AxisOptions<Scale, Value> = {
  // ...

  // 그리드 라인을 보여줄지/안보여줄지 여부
  showGridLines?: boolean;
  // 그리드 라인의 두께
  gridLineWidth?: number;
  // 그리드 라인의 색상
  gridLineColor?: string;
};

2.3.2. <XAxis />, <YAxis /> 수정

위에서 추가한 옵션들을 사용해 <XAxis />에 그리드 라인을 그리자.

comp/LineChart/LineChartBody/XAxis.tsx
// ...

function XAxis({
  // ...

  showGridLines = false,
  gridLineWidth = 1,
  gridLineColor = "lightgray",
}: XAxisProps) {
  // ...

  return (
    <G>
      {/* ... */}

      {showGridLines &&
        ticks.map((tick) => (
          <Line
            key={`${tick}`}
            x1={scale(tick)}
            x2={scale(tick)}
            y1={paneBoundary.y1}
            y2={paneBoundary.y2}
            stroke={gridLineColor}
            strokeWidth={gridLineWidth}
          />
        ))}
    </G>
  );
}

(<YAxis /> 도 코드가 거의 동일하므로 해당 코드는 생략한다.)

2.3.3. <LineChart /> 사용 시 옵션 적용 예

이제 그리드 라인도 자유롭게 변경할 수 있다.

<LineChart
  series={dummySeries}
  width="100%"
  height={200}
  xAxisOptions={{
    showGridLines: true,
    gridLineWidth: 2,
    gridLineColor: "rgba(0, 0, 0, 0.2)",
  }}
  // Y 축의 `showGridLines` 은 기본값이 `true`이다.
  // 보편적으로 Y 축의 그리드라인은 그리는 편이 많다는 (자의적인) 판단에 의해서다.
  yAxisOptions={{
    showGridLines: false,
  }}
/>

2.4. <Lines /> 옵션 구현

<Lines /> 는 옵션을 적용하는 방식이 다른 컴포넌트에 비해 하나 더 존재한다. 바로 series에 옵션 값을 받는 방법이다.

왜냐하면 <LineChart />series 에 데이터를 배열로 받아 여러 개의 라인을 그릴 수 있는데, 이 경우 라인들 전체에 대한 공통적인 옵션을 지정할 수도 있지만 그 중 특정 라인에 대해서만 별도의 옵션을 지정하고 싶을 수도 있기 때문이다.

예를 들어, 네 개의 라인이 그려지는 차트인데 그 중 하나가 특히 중요한 값이라 해당 시리즈의 라인만 굵게 표시하거나 특히 더 밝은 색상으로 표시하고 싶을 수 있다.

따라서 라인들 전체에 공통으로 적용할 옵션은 다른 컴포넌트들처럼 <LineChart /> 에서 옵션(linesOptions)을 받을 것이며, 특정 라인에만 적용할 옵션은 series 에 받을 것이다.

2.4.1. 옵션 타입 추가 및 수정

그러면 series 의 타입을 수정하고, 별도의 옵션 타입도 추가해보자.

comp/LineChart/types.ts
export type TimeSeries = {
  // 이 시리즈의 라인의 색상을 지정할 수 있다.
  color?: string;
  // 이 시리즈의 라인의 굵기를 지정할 수 있다.
  lineWidth?: number;
  data: { date: Date; value: number }[];
};

export type LinesOptions = {
  // - 라인에 적용될 색상을 배열로 받는다.
  // - 배열에 들어간 순서대로 라인에 색상이 적용된다.
  colors?: string[];
  // - 라인의 굵기는 단일값을 받는다.
  // - 만약 `colors` 처럼 배열로 받아서 차례로 지정하고 싶다면 그렇게 수정해도 된다.
  // - 다만 여기서는 선 굵기는 일일이 지정하는 경우가 적고,
  //  일일이 지정하고 싶다면 `TimesSeries`에 지정하는 게 편할 것이라는 (자의적인) 판단에 의해 단일값을 받는다.
  lineWidth?: number;
};

2.4.2. <LineChart />로부터 옵션 전달

<LineChart /> 로부터 <Lines /> 까지 옵션을 전달하는 것은 2.1.2. 와 거의 동일하다. 2.1.2. 혹은 3.1. 을 참고하자.

2.4.3. <Lines /> 수정

<Lines /> 의 옵션 적용 방식은 <XAxis />, <YAxis /> 와 약간 다르다. series 에 적용된 옵션값이 있으면 해당 값을 먼저 적용한 뒤에, linesOptions 로 받은 값들을 적용해야 한다.

comp/LineChart/LineChartBody/Lines.tsx
import { G, Path } from "react-native-svg";
import { LinesOptions, TimeSeries } from "../types";

const DEFAULT_COLORS = [
  'blue',
  'skyblue',
  'green',
  'brown',
  'gray',
  'orange',
  'purple',
  'red',
  'pink',
  'black',
];

type LinesProps = LinesOptions & {
  series: TimeSeries[];
  lineFunc: d3.Line<TimeSeries["data"][0]>;
};
function Lines({
  series,
  lineFunc,
  colors = DEFAULT_COLORS,
  lineWidth = 1,
}: LinesProps) {
  return (
    <G>
      {series.map((sr, i) => (
        <Path
          key={i}
          d={lineFunc(sr.data) ?? undefined}
          // series.color 를 먼저 확인하고 해당 값이 없으면 colors 로부터 색상 값을 가져온다.
          stroke={sr.color ?? colors[i % colors.length]}
          strokeLinecap="round"
          fill="transparent"
          // series.lineWidth 를 먼저 확인하고 해당 값이 없으면 lineWidth 로부터 굵기 값을 가져온다.
          strokeWidth={sr.lineWidth ?? lineWidth}
        />
      ))}
    </G>
  );
}

export default Lines;

2.4.4. <LineChart /> 사용 시 옵션 적용 예

이제 라인의 색상 및 굵기를 컴포넌트 사용자가 직접 지정할 수 있게 되었다.

<LineChart
  series={dummySeries}
  width="100%"
  height={200}
  xAxisOptions={{ showTicks: false, showGridLines: true }}
  yAxisOptions={{ showTicks: false, showGridLines: true }}
  linesOptions={{
    colors: ["red", "orange", "green", "blue"],
    lineWidth: 2,
  }}
/>

시리즈 별로 별도의 색상 혹은 굵기를 지정하는 것도 가능하다.

const dummySeriesWithCustom = [...dummySeries.map((sr) => ({ ...sr }))];
dummySeriesWithCustom[0].color = "black";
dummySeriesWithCustom[0].lineWidth = 4;

// ...

<LineChart
  series={dummySeriesWithCustom}
  width="100%"
  height={200}
  xAxisOptions={{ showTicks: false, showGridLines: true }}
  yAxisOptions={{ showTicks: false, showGridLines: true }}
  linesOptions={{
    colors: ["red", "orange", "green", "blue"],
    lineWidth: 2,
  }}
/>;

2.5. paneBoundary 관련 옵션 구현

바로 위 2.4.4. 에 그려진 라인차트를 보면 이상함을 느낄 수 있다.

X 축, Y 축의 틱을 모두 비활성화했는데 해당 영역까지 차트가 그려지는 게 아니라 여백으로 남아있는 걸 볼 수 있다.

틱의 유무를 판단해서 자동으로 여백이 계산되면 좋겠지만, 틱 및 틱라벨의 크기는 여러가지 변수가 많기도 하고 (틱의 길이, 틱라벨의 문자열 길이, 폰트 스타일 등) 틱의 크기와 별개로 사용자가 여백을 지정하고 싶을 수 있기 때문에, 자동으로 계산되게 하는 것보다는 사용자가 지정하게 하는 것이 나을 수도 있다.

그래서 여기서는 여백을 옵션으로 지정할 수 있게 하겠다. <LineChart /> 에서 paneBoundary 에 값을 넣어줄 때 하드코딩했던 여백들을 옵션으로 지정할 수 있게끔 할 것이다.

2.5.1. 옵션 타입 추가

comp/LineChart/types.ts
export type PaneOptions = {
  // 상하좌우에 모두 적용되는 여백.
  // 만약 각 방향에 대한 여백값이 따로 지정되면 이 값은 무시된다.
  margin?: number;
  // 상단의 여백.
  // `margin` 이 지정되어 있더라도 이 값을 지정하면 이 값이 우선적으로 적용된다.
  marginTop?: number;
  // 왼쪽의 여백.
  // `margin` 이 지정되어 있더라도 이 값을 지정하면 이 값이 우선적으로 적용된다.
  marginLeft?: number;
  // 오른쪽의 여백.
  // `margin` 이 지정되어 있더라도 이 값을 지정하면 이 값이 우선적으로 적용된다.
  marginRight?: number;
  // 하단의 여백.
  // `margin` 이 지정되어 있더라도 이 값을 지정하면 이 값이 우선적으로 적용된다.
  marginBottom?: number;
};

2.5.2. <LineChart /> 수정

이 옵션의 경우 하위 컴포넌트로 전달해줄 필요가 없이 <LineChart /> 에서 바로 사용하면 된다.

comp/LineChart/index.tsx
type LineChartProps = {
  // ...
  paneOptions?: PaneOptions;
};
function LineChart({
  // ...
  paneOptions = {},
}: LineChartProps) {
  // ...

  const { margin, marginTop, marginLeft, marginRight, marginBottom } =
    paneOptions || {};

  // `paneOptions` 의 값들을 활용해 `paneBoundary` 값을 계산한다.
  const updatePaneBoundary = (width?: number, height?: number) => {
    setState(dr => {
      if (width !== undefined) {
        dr.width = Math.round(width);
      }
      if (height !== undefined) {
        dr.height = Math.round(height);
      }

      dr.paneBoundary = new PaneBoundary({
        x1: marginLeft ?? margin ?? DEFAULT_Y_AXIS_WIDTH,
        x2: dr.width - (marginRight ?? margin ?? 10),
        y1: dr.height - (marginBottom ?? margin ?? DEFAULT_X_AXIS_HEIGHT),
        y2: marginTop ?? margin ?? 10,
      });
    });
  };

  // `paneOptions` 의 여백 값이 하나라도 바뀌면 `updatePaneBoundary()` 를 호출한다.
  useEffect(() => {
    if (!state.width || !state.height) {
      return;
    }
    updatePaneBoundary();
  }, [margin, marginTop, marginLeft, marginRight, marginBottom]);

  // `onLayout` 이벤트 리스너가 실행되면 `updatePaneBoundary()` 를 호출한다.
  const onLayout: SvgProps['onLayout'] = evt => {
    const { layout } = evt.nativeEvent;
    updatePaneBoundary(layout.width, layout.height);
  };

  // ...

  return (
    // ...
  );
}

export default LineChart;

2.5.3. <LineChart /> 사용 시 옵션 적용 예

<LineChart
  series={dummySeries}
  width="100%"
  height={200}
  xAxisOptions={{ showTicks: false, showGridLines: true }}
  yAxisOptions={{ showTicks: false, showGridLines: true }}
  paneOptions={{ margin: 4 }}
/>

이제 차트가 약간의 여백만 가지도록 그려진다.

3. 결과

이로서 라인차트의 옵션 기능을 구현해보았다. <LineChart /> 컴포넌트 사용자는 요구사항이나 취향에 따라 차트를 다양한 형식으로 사용할 수 있게 되었다.

위에서 제공하는 옵션 항목들은 어디까지나 예시일 뿐, 개발자의 취향에 따라 몇몇 항목은 제외할 수도 있고 몇몇 항목은 추가할 수도 있다.

예를 들어 X 축의 라벨 위치를 조정하는 offset 을 옵션으로 넣고 싶다면 AxisOptionstickLabelOffsetX 등의 값을 추가할 수 있을 것이다.

3.1. 상세 코드 전문

본문에 삽입된 코드들은 생략된 부분들이 있으므로 코드 전문을 보고 싶다면 아래 링크를 참고하자.

3.2. 실행해보기

구현 결과를 실기기 혹은 에뮬레이터에서 직접 확인하고 싶다면 https://github.com/ricale/D3ChartExamples 에서 프로젝트를 받아서 실행해보면 된다.

# 안드로이드
git clone https://github.com/ricale/D3ChartExamples.git
cd ./D3ChartExamples
yarn
yarn android
# iOS
git clone https://github.com/ricale/D3ChartExamples.git
cd ./D3ChartExamples
yarn
cd ./ios && pod install && cd ../
yarn ios

4. 다음

다음에 구현 및 정리할 예정인 작업들은 아래와 같다.

  1. 라인차트 레전드 기능 구현
  2. 라인차트 특정 아이템 선택 기능 구현 (터치 이벤트 활용)
  3. 라인차트 애니메이션 기능 구현 (초기화 시, 값 변경 시 등)
  4. 컬럼차트 구현 (상세 계획 미정)
  5. 파이차트 구현 (상세 계획 미정)