๐Ÿ“Šrecharts ์ด์šฉํ•˜์—ฌ ์ฐจํŠธ ๋งŒ๋“ค๊ธฐ

hyochoยท2023๋…„ 3์›” 14์ผ
0

React

๋ชฉ๋ก ๋ณด๊ธฐ
8/23
post-thumbnail

recharts ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์ด์šฉํ•œ bar, area ๊ฐ€ ํ˜ผํ•ฉ๋œ ์ฐจํŠธ ๋งŒ๋“ค๊ธฐ

๐Ÿ™„์ฐจํŠธ๊ฐ€ ์•ˆ๋ณด์—ฌ์š”

  <div style={{ width: 2000, height: 800 }}>
      <h3>์ฐจํŠธ</h3>
      <ResponsiveContainer width="100%" height="100%">
        <ComposedChart
          width={600}
          height={400}
          data={chartData}
          margin={{
            top: 20,
            right: 20,
            bottom: 20,
            left: 20,
          }}
        >

recharts ๊ณต์‹ ๋ฌธ์„œ์— ์žˆ๋Š” ์˜ˆ์ œ์ž‘์„ ๊ฐ€์ ธ์™”๋Š”๋ฐ ์™œ ์ฐจํŠธ๊ฐ€ ์•ˆ๋‚˜์˜ค๋Š”์ง€ ๋ชฐ๋ผ์„œ ๋‹นํ™ฉํ–ˆ๋‹ค. ์—ฌ๊ธฐ์„œ ResponsiveContainer ๊ฐ€ ๋ถ€๋ชจ ์š”์†Œ์˜ 100%์ด๊ธฐ ๋•Œ๋ฌธ์— ์ƒ์œ„ ํƒœ๊ทธ์—์„œ ํฌ๊ธฐ๋ฅผ ์ฃผ์–ด์•ผ ํ–ˆ๋‹ค.

๐Ÿ™„ ์–ด๋ผ..? ์ฐจํŠธ์˜ ํ˜•ํƒœ๊ฐ€..?

    <Area
            type="monotone"
            dataKey="value_area"
            fill="#8884d8"
            stroke="#8884d8"
            yAxisId="left"
          />
          <Bar dataKey="value_bar" barSize={20} yAxisId="right" fill="pink" />

๋ถ„ํ™์ƒ‰ bar ์— ๊ฐ€๋ ค์ ธ area ๋ถ€๋ถ„์ด ์ž˜ ๋ณด์ด์ง€ ์•Š์•˜๋‹ค. ์›๋ž˜ ์ด๋Ÿฐ๊ฑธ๊นŒ? ํ–ˆ์ง€๋งŒ ๊ทธ๋Ÿด๋ฆฌ ์—†์ง€

          <Bar dataKey="value_bar" barSize={20} yAxisId="right" fill="pink" />
          <Area
            type="monotone"
            dataKey="value_area"
            fill="#8884d8"
            stroke="#8884d8"
            yAxisId="left"
          />

Bar ์™€ Area์˜ ์ˆœ์„œ๋งŒ ๋ฐ”๊ฟ”์ฃผ๋ฉด ๊ณ ์น  ์ˆ˜ ์žˆ๋‹ค.

๐Ÿ™„ ์ด์™•์ด๋ฉด ์˜ˆ์œ ํˆดํŒ


hover ์‹œ์— ๊ฐ๊ฐ์˜ ์ •๋ณด๋ฅผ ๋‚˜ํƒ€๋‚ด ์ฃผ๋Š” ๊ฒƒ์„ ํˆดํŒ์ด๋ผ๊ณ  ํ•œ๋‹ค.
recharts ์—์„œ๋Š” ๊ธฐ๋ณธ ํˆดํŒ์„ ์ œ๊ณตํ•ด์ฃผ์ง€๋งŒ ๋ฌ˜ํ•˜๊ฒŒ? ๋ชป์ƒ๊ฒผ๋‹ค.


๊ณต์‹๋ฌธ์„œ์— ์ปค์Šคํ…€ ํ•˜๋Š” ๋ฐฉ์‹๋„ ์ž˜ ๋‚˜์™€์žˆ๋‹ค. id๋กœ ๋ฐ›๋Š” ๊ตฌ ์ด๋ฆ„ ์•„๋ž˜๋กœ map ๋ฐ˜๋ณต๋ฌธ์„ ์‚ฌ์šฉํ•ด flex๋ฅผ ์ฃผ๊ณ  ์ˆ˜์น˜๋ฅผ ๊ฐ๊ฐ์˜ bar์™€ area ์ปฌ๋Ÿฌ๋กœ ์„ค์ •ํ–ˆ๋‹ค.


๊ทธ๋ฆฌ๊ณ  focus๋˜๋ฉด ์‚ฌ์ง„์ฒ˜๋Ÿผ ์ž๋™์œผ๋กœ ๋ณด๋”๊ฐ€ ์ƒ๊ธฐ๋Š”๋ฐ,

.recharts-tooltip-wrapper:focus-visible {
  outline: none;
}

์ด ์†์„ฑ์„ ์ฃผ๋ฉด ์—†์•จ ์ˆ˜ ์žˆ๋‹ค.

๐Ÿšจํƒ€์ž… ์˜ค๋ฅ˜

์ฒ˜์Œ์— ํŒŒ์ผ์„ ๋งŒ๋“ค๋•Œ ์‹ค์ˆ˜๋กœ jsx ๋กœ ๋งŒ๋“ค์–ด ์˜ค๋ฅ˜ ํ•˜๋‚˜ ์—†์ด ์ž˜ ๋‚˜๊ฐ€๋‹ค๊ฐ€, ์‚ฌ์‹ค์„ ๊นจ๋‹ซ๊ณ  tsx๋กœ ๋ฐ”๊พธ์ž ๋งˆ์ž ๋นจ๊ฐ„์ค„ ํŒŒํ‹ฐ๊ฐ€ ๋‚ฌ๋‹ค.. ๐Ÿ˜ญ

const CustomTooltip = ({
  active,
  payload,
}: {
  active: boolean;
  payload: any[];
}) => {
  if (active && payload && payload.length) {
    return (

payload๋Š” ์ปค์„œ๋ฅผ ์ฐจํŠธ์— ๊ฐ–๋‹ค๋Œ€๋ฉด ๋‚˜์˜ค๋Š” ์ •๋ณด์ธ๋ฐ ์–ด๋–ป๊ฒŒ ์ฒ˜๋ฆฌํ•ด์ค˜์•ผ ํ•  ์ง€ ๋ชฐ๋ผ any[] ๋กœ ๋’€์—ˆ๋‹ค.

import { TooltipProps } from 'recharts';

const CustomTooltip = ({
  active,
  payload,
}: TooltipProps<number, string>): JSX.Element | null => {
  if (active && payload && payload.length) {
    return (

์•Œ๊ณ  ๋ณด๋‹ˆ recharts ์—์„œ ์ œ๊ณตํ•˜๋Š” TooptipProps ์ด ์žˆ์–ด์„œ ์ ์šฉ์‹œ์ผœ ์คฌ๋‹ค.

์ „๊ฐœ์—ฐ์‚ฐ์ž ์˜ค๋ฅ˜

Type 'Set<any>' is not an array type or a string type. Use compiler option '--downlevelIteration' to allow iterating of iterators.

์ด ์˜ค๋ฅ˜๋Š” ES6๋ฅผ ์ง€์›ํ•˜์ง€ ์•Š๋Š” ํ™˜๊ฒฝ์—์„œ ES6 ์—ด๊ฑฐํ˜•(Iterable) ๊ฐ์ฒด๋ฅผ ์—ด๊ฑฐํ•˜๋ ค๊ณ  ํ•  ๋•Œ ๋ฐœ์ƒํ•œ๋‹ค. ์˜ˆ๋ฅผ๋“ค์–ด Set ํ˜น์€ Map ๊ฐ์ฒด๋Š” ES6์—์„œ ์ถ”๊ฐ€๋œ ๊ฐ์ฒด์ธ๋ฐ tsconfig.json์— target์„ ES5๋กœ ์„ค์ •ํ•œ ์ƒํƒœ์—์„œ Set์„ ์—ด๊ฑฐํ•˜๋ ค๊ณ  ํ•˜๋ฉด ์ด๋Ÿฌํ•œ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•˜๋Š” ๊ฒƒ์ด์—ˆ๋‹ค.
ํ•ด๊ฒฐํ•˜๋Š” ๋ฐ์—๋Š” ๋‘๊ฐ€์ง€ ์˜ค๋ฅ˜ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•์ด ์žˆ๋Š”๋ฐ ๋‚˜๋Š” ๊ฐ„๋‹จํžˆ --downlevelIteration์„ ์‚ฌ์šฉํ•˜์—ฌ TypeScript๋ฅผ ๋นŒ๋“œํ–ˆ๋‹ค.

refactor

bar๊ณผ area ์˜ ๊ฐ’์„ useState๋กœ ๊ด€๋ฆฌํ–ˆ๋Š”๋ฐ, ํ•„ํ„ฐ๋ง ๊ธฐ๋Šฅ์„ ์ถ”๊ฐ€ํ•˜๊ฒŒ ๋˜๋ฉด์„œ ๋ฆฌํŒฉํ† ๋ง์„ ํ•˜๊ธฐ๋กœ ํ–ˆ๋‹ค. ์ƒˆ๋กœ๊ณ ์นจํ•ด๋„ ํ•„ํ„ฐ๋ง ์ •๋ณด๊ฐ€ ์‚ฌ๋ผ์ง€์ง€ ์•Š๊ฒŒ ํ•˜๊ธฐ ์œ„ํ•ด react-router-dom ์—์„œ ์ œ๊ณตํ•˜๋Š” useSearchParams๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊ฐ’์„ ์ €์žฅํ•ด์ฃผ๊ธฐ๋กœ ํ–ˆ๋‹ค.

์ดˆ๊ธฐ๊ฐ’์„ ๋„ฃ์–ด์ฃผ์ง€ ์•Š์•„์„œ ์ฒซ ํ™”๋ฉด์ด ๋นˆ ํ™”๋ฉด์œผ๋กœ ๋‚˜ํƒ€๋‚ฌ๋‹ค. useEffect ์•ˆ์— ์ดˆ๊ธฐ๊ฐ’์ด ์—†์„ ๋•Œ์— ์ „์ฒด ์„ ํƒ์„ ํ•ด์ค€๋‹ค๋Š” ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์—ฌ ์ฒซ ํ™”๋ฉด์—์„œ district ์™€ category์˜ ์ „์ฒด๊ฐ’์œผ๋กœ ์„ ํƒํ•ด์ฃผ๊ฒŒ ๋งŒ๋“ค์—ˆ๋‹ค.

  useEffect(() => {
    if (!district && !category) {
      searchParams.set('district', '์ „์ฒด');
      searchParams.set('category', '์ „์ฒด');
    }
  }, []);


์ฃผ์†Œ์ฐฝ์„ ๋ณด๋ฉด ํด๋ฆญ์— ๋”ฐ๋ผ category ์™€ district ๊ฐ€ ๋ณ€ํ•˜๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ๋‹ค!

profile
๊ธฐ๋กํ•˜๋Š” ์Šต๊ด€์„ ๊ธฐ๋ฅด๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

0๊ฐœ์˜ ๋Œ“๊ธ€