๐Ÿ”ฎ ์†Œ๋งˆ๋ฒ• ํ”„๋กœ์ ํŠธ -3 (graph)

๋ฐ์ดํ„ฐ๋“ค์„ ๊ทธ๋ž˜ํ”„๋กœ ํ‘œํ˜„ํ•ด๋ณด๊ณ  ์‹ถ์–ด์„œ ๋งŒ๋“ค์–ด๋ณด์•˜๋‹ค.

๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ  canvas๋ฅผ ์ด์šฉํ•ด์„œ ๋งŒ๋“ค์–ด๋ณด๋ ค๊ณ  ํ–ˆ๋Š”๋ฐ ๋งŒ๋“ค๊ณ ๋‚˜์„œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์— ๋Œ€ํ•œ ๊ฒฌํ•ด๊ฐ€ ๋‹ฌ๋ผ์ง€๊ฒŒ ๋˜์—ˆ๋‹ค.


#. Project Map


์ œ์ž‘๋…ธํŠธ ํ•œ๋ˆˆ์—๋ณด๊ธฐ[์ ‘๊ธฐ/ํŽผ์น˜๊ธฐ]

1. ๋ ˆ์ด์•„์›ƒ

graph

๊ทธ๋ž˜ํ”„๋Š” canvas๋ฅผ ์ด์šฉํ•ด ๊ทธ๋ ธ๊ณ 

๋ฐ์ดํ„ฐ๋ฅผ ์ถ”๊ฐ€, ์ˆ˜์ •, ์‚ญ์ œํ•  ์ˆ˜ ์žˆ๋Š” form Input ์š”์†Œ๋“ค๋„ ๋„ฃ์–ด์ฃผ์—ˆ๋‹ค.

๊ทธ๋ฆฌ๊ณ  ์ดˆ๊ธฐ ๋ฐ์ดํ„ฐ๋กœ ์ž„์˜์˜ ํ‚ค๋“ค๊ณผ ๊ฐ’๋“ค์„ ๋„ฃ์–ด์ฃผ์—ˆ๋‹ค.

2. ์ƒํƒœ๊ด€๋ฆฌ

const data = useSelector(state => state.graph)
const [graphData, setGraphData] = useState(
  data.graphData || {
    Austrailia: 500,
    India: 1800,
    USA: 500,
    Brasil: 2100,
    China: 1500,
  }
)
const entries = Object.entries(graphData)

const [country, setCountry] = useState(data.country || '')
const [population, setPopulation] = useState(data.population || '')
const [country2, setCountry2] = useState('')
const [errorMessage, setErrorMessage] = useState('')
const inputRef1 = useRef()
const inputRef2 = useRef()

2-1. ๋ฆฌ์•กํŠธ hooks

์ปดํฌ๋„ŒํŠธ๋ฅผ class ๊ฐ€ ์•„๋‹Œ ํ•จ์ˆ˜ํ˜•์œผ๋กœ ๊ตฌ์„ฑํ•˜๊ณ  ํด๋กœ์ €, hooks๋ฅผ ์ด์šฉํ•ด์„œ ์ปดํฌ๋„ŒํŠธ ๋‚ด์—์„œ์˜ ์ƒํƒœ๊ด€๋ฆฌ๋ฅผ ํ•˜์˜€๋‹ค.

์ƒํƒœ๋ณ€์ˆ˜๋Š”

  1. graphData : ๊ทธ๋ž˜ํ”„์— ๋“ค์–ด์žˆ๋Š” ๊ฐ์ฒด ํ˜•ํƒœ์˜ ๋ฐ์ดํ„ฐ
  1. country : ์ถ”๊ฐ€, ์ˆ˜์ •์„ ์œ„ํ•œ ํ‚ค๊ฐ’
  1. country2 : ์‚ญ์ œ๋ฅผ ์œ„ํ•œ ํ‚ค๊ฐ’
  1. population : ์ถ”๊ฐ€, ์ˆ˜์ •์„ ์œ„ํ•œ ๋ฐธ๋ฅ˜๊ฐ’
  1. errorMessage : ์—๋Ÿฌ๋ฉ”์‹œ์ง€๋ฅผ ๋‹ด์€ string ๊ฐ’

์ดˆ๊ธฐ ๋ฐ์ดํ„ฐ๋ฅผ ๋‚˜๋ผ์™€ ์ธ๊ตฌ๋กœ ํ•ด์„œ country, population์œผ๋กœ key, value ๊ฐ’๋“ค์„ ๋ณ€์ˆ˜ ๋„ค์ด๋ฐ ํ•ด์คฌ๋‹ค.

๊ทธ๋ฆฌ๊ณ  object์˜ ํ‚ค๊ฐ’๊ณผ ๋ฐธ๋ฅ˜๊ฐ’์„ ๊ด€๋ฆฌํ•˜๊ธฐ ์œ„ํ•ด entries ๋ณ€์ˆ˜๋ฅผ ๋งŒ๋“ค์–ด์คฌ๊ณ 

form submit ํ›„์˜ ์ž๋™ input focus๋ฅผ ์œ„ํ•ด inputRef1, inputRef2 ๋ณ€์ˆ˜๋„ ๋งŒ๋“ค์–ด์ฃผ์—ˆ๋‹ค.

2-2. ๋ฆฌ๋•์Šค hooks

์ฒ˜์Œ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๊ตฌ์„ฑํ•  ๋•Œ, graph reducer์˜ ์ดˆ๊ธฐ๊ฐ’์„ ๊ฐ€์ ธ์˜ค๋„๋ก ์„ค์ •ํ–ˆ๋‹ค.

๊ทธ๋ฆฌ๊ณ  ๋‹ค๋ฅธ ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์“ฐ๊ธฐ ์œ„ํ•ด ์ด ๊ทธ๋ž˜ํ”„ ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์ž ์‹œ Docker์— ๋„ฃ์—ˆ์„ ๋•Œ, ํ˜„์žฌ ๋ฆฌ์•กํŠธ state๋ฅผ ๋ฆฌ๋•์Šค state๋กœ ์˜ฎ๊ธฐ๋„๋ก ์„ค์ •ํ–ˆ๋‹ค.

์ฆ‰, ํ•ด๋‹น ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ state ์กฐ์ž‘์€ ๋ฆฌ์•กํŠธ๋กœ,

ํ•ด๋‹น ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ state ์ž„์‹œ์ €์žฅ( Docker ๋„˜๊ธฐ๊ธฐ )์€ ๋ฆฌ๋•์Šค๋กœ ํ•˜์˜€๋‹ค.

3. Canvas

3-1. useEffect

useEffect(() => {
  let canvasElem = document.querySelector('canvas')
  canvasElem.width = 840
  canvasElem.height = 320
  const ctx = canvasElem.getContext('2d')
  drawGrid(canvasElem, ctx)
  drawAxis(ctx)
  drawChart(ctx)
})

์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋งˆ์šดํŒ… ๋œ ํ›„ useEffect hooks๋ฅผ ํ†ตํ•˜์—ฌ canvas๋ฅผ ๊ทธ๋ฆฌ๋„๋ก ํ•˜์˜€๋‹ค.

3-2. drawGrid()

const drawGrid = (canvasElem, ctx) => {
  let xGrid = 10
  let yGrid = 10
  let cellSize = 10
  ctx.beginPath()
  while (xGrid < canvasElem.height) {
    ctx.moveTo(0, xGrid)
    ctx.lineTo(canvasElem.width, xGrid)
    xGrid += cellSize
  }
  while (yGrid < canvasElem.width) {
    ctx.moveTo(yGrid, 0)
    ctx.lineTo(yGrid, canvasElem.height)
    yGrid += cellSize
  }
  ctx.strokeStyle = '#ccc'
  ctx.stroke()
}

์ „์ฒด ๊ทธ๋ฆฌ๋“œ๋ฅผ ๊ทธ๋ ค์ฃผ๋Š” ํ•จ์ˆ˜ ์ž‘์„ฑ

3-3. drawAxis()

const drawAxis = ctx => {
  let yPlot = 300
  let pop = 0

  ctx.beginPath()
  ctx.strokeStyle = 'black'
  ctx.moveTo(50, 50)
  ctx.lineTo(50, 300)
  ctx.lineTo(800, 300)
  ctx.moveTo(50, 300)

  for (let i = 0; i < 10; i++) {
    ctx.strokeText(pop, 20, yPlot)
    pop += 500
    yPlot -= 50
  }
  ctx.stroke()
}

x์ถ•๊ณผ y์ถ•, x์ถ•์˜ ๊ฐ’๋“ค์„ ๊ทธ๋ ค์ฃผ๋Š” ํ•จ์ˆ˜ ์ž‘์„ฑ

3-4. drawChart()

const drawChart = ctx => {
  ctx.beginPath()
  ctx.strokeStyle = 'black'
  ctx.moveTo(50, 300)
  ctx.font = 'bold normal 10px Verdana'

  var xPlot = 100
  let i = 0

  for (const [country, population] of entries) {
    var populationInBlocks = population / 10
    ctx.fillText(
      country + '(' + population + ')',
      xPlot,
      i % 2 === 0
        ? 300 - populationInBlocks - 10
        : 300 - populationInBlocks - 10 + 30
    )
    ctx.lineTo(xPlot, 300 - populationInBlocks)
    ctx.arc(xPlot, 300 - populationInBlocks, 2, 0, Math.PI * 2, true)
    xPlot += 50
    i++
  }
  ctx.stroke()
}

๊ทธ๋ž˜ํ”„๋ฅผ ๊ทธ๋ฆฌ๋Š” ํ•จ์ˆ˜ ์ž‘์„ฑ

3-5. โญ ๋ณ€์ˆ˜ i

๊ทธ๋ž˜ํ”„์˜ ๊ฐ๊ฐ์˜ ํ…์ŠคํŠธ ๊ฐ’๋“ค์ด ๋™์ผํ•œ ์œ„์น˜์— ์œ„์น˜ํ•  ๊ฒฝ์šฐ ํ…์ŠคํŠธ๊ฐ€ ๊ฒน์น˜๋Š” ํ˜„์ƒ์ด ์žˆ์—ˆ๋‹ค. ๊ทธ๋ž˜์„œ ๋ณ€์ˆ˜ i๋ฅผ ํ• ๋‹นํ•˜๊ณ  ์ง์ˆ˜์ผ ๊ฒฝ์šฐ๋Š” ๊ผญ์ง€์  ๋ฐ‘์— ํ…์ŠคํŠธ๋ฅผ ์ ๊ณ  ํ™€์ˆ˜์ผ ๊ฒฝ์šฐ๋Š” ๊ผญ์ง€์  ์œ„์— ํ…์ŠคํŠธ๋ฅผ ์ ๊ฒŒํ•ด์„œ ๊ฒน์น˜๋Š” ํ˜„์ƒ์„ ํ”ผํ•ด์ฃผ์—ˆ๋‹ค.

4. ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ

4-1. submit ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ

const submitHandler1 = e => {
  console.log(graphData)
  e.preventDefault()

  if (
    isNaN(Number(population)) ||
    Number(population) > 2500 ||
    Number(population) < 0
  ) {
    setErrorMessage('0~2500์‚ฌ์ด์˜ ๊ฐ’์„ ์ž…๋ ฅํ•˜์„ธ์š”.')
    return
  }
  let obj = { ...graphData }
  obj[country] = Number(population)
  // obj.county = population not same!!
  let graphData_temp = Object.assign({}, obj)
  setGraphData(graphData_temp)
  setCountry('')
  setPopulation('')
  setErrorMessage('')
  inputRef1.current.focus()
}

4-2. โญ isNaN, number condition

๊ฐ’ input์˜ ๊ฐ’์ด ์ˆซ์ž๊ฐ€ ์•„๋‹ ๊ฒฝ์šฐ, ๊ทธ๋ฆฌ๊ณ  ์ž…๋ ฅ๊ฐ’์˜ ๋ฒ”์œ„๊ฐ€ canvas ๋ฅผ ๋„˜์„ ๊ฒฝ์šฐ ํ•ธ๋“ค๋งํ•˜๋Š” ์กฐ๊ฑด๋ฌธ์„ ๋„ฃ์–ด์ฃผ์—ˆ๋‹ค.

๊ทธ๋ฆฌ๊ณ  ์กฐ๊ฑด๋ฌธ์— ๊ฑธ๋ฆด ๊ฒฝ์šฐ, ์—๋Ÿฌ๋ฉ”์‹œ์ง€๋ฅผ ์ถœ๋ ฅํ•˜๊ฒŒ๋” ํ•ด์ฃผ์—ˆ๋‹ค.

4-3. โญ โญ obj

ํ•ด๋‹น ํ‚ค์™€ ๊ฐ’์ด ์ •์ƒ์ ์ผ ๊ฒฝ์šฐ, state๋ฅผ ๋ถˆ๋ณ€์„ฑ์„ ์œ ์ง€ํ•˜๋ฉด์„œ ์—…๋ฐ์ดํŠธํ•ด์ฃผ๋ ค ํ–ˆ๋‹ค.

์ƒˆ๋กœ์šด obj ๋ณ€์ˆ˜์— spread ๋ฌธ๋ฒ•(โ€ฆ)์„ ํ†ตํ•ด ๊ธฐ์กด state๋ฅผ ์ดˆ๊ธฐํ™”ํ•ด์ฃผ์—ˆ๊ณ  obj์— ์ƒˆ๋กœ์šด ํ‚ค, ๊ฐ’์„ ๋„ฃ์–ด์ค€ ๋’ค ์—…๋ฐ์ดํŠธ ํ•ด์ฃผ์—ˆ๋‹ค.

์—ฌ๊ธฐ์„œ ์ƒˆ๋กญ๊ฒŒ ์•ˆ ์ ์€,

obj.key = value๋ฅผ ํ†ตํ•œ obj ์—…๋ฐ์ดํŠธ๋Š” ์‹ค์ œ๋กœ key๊ฐ’์œผ๋กœ key๊ฐ€ ๋“ค์–ด๊ฐ”๋‹ค.

์ด๋ฅผ ํ”ผํ•˜๊ธฐ ์œ„ํ•ด์„œ obj[key] = value๋ฅผ ์ด์šฉํ•ด์•ผ ์ •์ƒ์ ์œผ๋กœ key์˜ string๊ฐ’์ด key๋กœ ๋“ค์–ด๊ฐ”๋‹ค.

delete ์˜ ๊ฒฝ์šฐ๋„ ๋งˆ์ฐฌ๊ฐ€์ง€๋‹ค. delete obj[country2]

5. ๊ฐœ์ธ์ ์ธ ํ”ผ๋“œ๋ฐฑ

5-1. canvas๋Š” read only

์‚ฌ์‹ค ๊ทธ๋ž˜ํ”„ ์•ˆ์— ์žˆ๋Š” ์„ ์ด๋‚˜ ์ ์— ์ด๋ฒคํŠธ๋ฅผ ์ถ”๊ฐ€ํ•˜๊ณ ์‹ถ์—ˆ๋‹ค.

๊ทผ๋ฐ canvas๋Š” ํ•จ์ˆ˜๋ฅผ ํ†ตํ•ด ๋„ํ˜•์„ ๊ทธ๋ฆด ๋ฟ DOM์ฒ˜๋Ÿผ ์ง์ ‘ ์•ˆ์˜ ๋„ํ˜•์— ์ด๋ฒคํŠธ๋ฅผ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์—†๋‹ค.

์‹ค์ œ canvas์— ์ด๋ฒคํŠธ๋ฅผ ๊ฑธ๋ ค๋ฉด canvas ์ž์ฒด์— ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ๋ฅผ ๋‹ฌ๊ฑฐ๋‚˜ ํ•ด์•ผํ•œ๋‹ค.

์•„์ง canvas๋ฅผ ์ž์œ ์ž์žฌ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†๋Š” ์‹ค๋ ฅ์ด๊ธฐ๋„ ํ•˜๊ณ  canvas ๋‚ด๋ถ€์˜ ๋„ํ˜•์— ์ด๋ฒคํŠธ๋ฅผ ๋„ฃ์ง€ ๋ชปํ•œ๊ฒŒ ์ข€ ์•„์‰ฝ๋‹ค.

5-2. ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ

๊ทธ๋ž˜ํ”„์— ์ด๋ฒคํŠธ๋ฅผ ๋„ฃ๋Š” ๊ฐ€์žฅ ์‰ฌ์šด ๋ฐฉ๋ฒ•์€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด๋‹ค.

๋‹ค์Œ ํ”„๋กœ์ ํŠธ๋กœ ๊ทธ๋ž˜ํ”„, ์ฐจํŠธ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•ด๋ณด๊ณ  ์‹ถ๋‹ค.

5-3. ๋””์ž์ธ

๋””์ž์ธ์ด ๋งค์šฐ ๊ตฌ๋ฆฌ๋‹ค..!

5-4. ๋„ค์ด๋ฐ

๋ณ€์ˆ˜๋ฅผ country, population ์ฒ˜๋Ÿผ ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๋ชฉ์ ์„ฑ์ด ๋‹ค์†Œ ๋งž์ง€ ์•Š๋Š” ๋„ค์ด๋ฐ์„ ํ–ˆ๋‹ค.

  1. ์ดˆ๊ธฐ ๋ฐ์ดํ„ฐ๋ฅผ ๋‚˜๋ผ์™€ ์ธ๊ตฌ๋กœ ํ•ด์„œ
  2. ์‚ฌ์šฉ์ž์—๊ฒŒ๋Š” ๋ณด์ด์ง€ ์•Š๊ณ  ๊ฐœ๋ฐœ์ž ์ž…์žฅ์—์„œ ์ดํ•ดํ•  ์ˆ˜ ์žˆ์œผ๋ฉด ๋œ๋‹ค.

๋ผ๋Š” ํ•ฉ๋ฆฌํ™”๋ฅผ ํ•  ์ˆ˜ ์žˆ๊ฒ ์ง€๋งŒ,

country, country2 ์ด๋Ÿฐ ๋ณ€์ˆ˜ ๋„ค์ด๋ฐ์€ ํ•ฉ๋ฆฌํ™” ์ž์ฒด๋„ ๋ถˆ๊ฐ€๋Šฅํ•˜๋‹ค.

๊ฐœ๋ฐœ์ž๋„ ํ—ท๊ฐˆ๋ฆฌ๊ธฐ ๋•Œ๋ฌธ..

ํ•ด๋‹น ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ๊ธฐ๋Šฅ์ด ๋งŽ์€ ํŽธ์ด ์•„๋‹ˆ๋ผ์„œ ๋„ค์ด๋ฐ์„ ์ˆ˜์ •ํ•˜์ง€๋Š” ์•Š์•˜์ง€๋งŒ, ์ฒ˜์Œ ๋ณ€์ˆ˜ ๋„ค์ด๋ฐ์„ ์ง€์„ ๋•Œ ๊ทธ๋ž˜๋„ ์ข€ ๋” ์‹ ๊ฒฝ์“ฐ๋Š” ์Šต๊ด€์„ ๋“ค์—ฌ์•ผ๊ฒ ๋‹ค.


Written by@taenyKim
๋ฐฐ์šฐ๋ฉฐ ์„ฑ์žฅํ•˜๊ณ  ๊ธฐ๋กํ•˜๊ธฐ #FE #UI #๊ฐœ๋ฐœ #life

GitHubFacebook