SparkLine Chart 만들기 (2)

November 29, 2021

0. 목표

  • 마우스 포인터 기준으로, 해당 포지션의 **데이터(차트)**를 보여주자

    chart-example1 chart-example2

    1. 데이터를 표현할 Tag를 만들어주자

    const $line = document.createElementNS('http://www.w3.org/2000/svg', 'line')
    const $spot = document.createElementNS(
        'http://www.w3.org/2000/svg',
        'circle'
    )

    2. lineChart의 Path(X, Y좌표)를 이용하자

    • 기존 데이터를 사용하는 대신, **lineChart를 그린 데이터(비율로 계산)**의 좌표를 활용
      • 비율대로 계산된 데이터(X, Y 좌표) === 마우스 포인터의 위치(X, Y 좌표)
        const dataPoints = []
        
        data.forEach((value, index) => {
            const x = index * offset
            const y = getY(max, height, strokeWidth, value)
        
            pathCoords += `L ${x} ${y},`
        
            // element의 너비와 높이 비율에 따라 계산된 좌표를 넣어준다.
            dataPoints.push({ x, y })
        })

    3. Mouse(Touch)의 위치

    1. Mouse Event(offset)

      • event를 binding한 element부터 현재 포인트의 위치를 알 수 있다.
      const handleMouseMove = (e) => {
          const position = {
              x: e.offsetX,
              y: e.offsetY,
          }
          draw(position)
      }
    2. Touch Event(touchs.client, getBoundingClientRect)

      • touch 이벤트의 touches property와 getBoundingClientRect를 활용
      const handleTouchMove = (e) => {
          const rect = svg.getBoundingClientRect()
          const position = {
              x: e.touches[0].clientX - rect.left,
              y: e.touches[0].clientY - rect.top,
          }
          draw(position)
      }

    3. mouse position을 가지고, 가장 가까운 좌표를 찾는다.

    1. mouse position보다 큰 좌표(chart Line) 좌표를 찾는다
      • 만약 없다면, 마지막 좌표라고 가정한다.
    2. 1번에서 찾은 데이터로, 이전 좌표(chart Line)를 찾는다.
    3. 찾은 좌표들을 활용하여, 더 가까운곳을 Target 좌표라고 가정한다.
      • 3번 데이터가 없다면, 첫번째 데이터라고 가정한다.
    4. Target 좌표를 가지고 포인터와 라인 Tag를 그려준다.
    const draw = (position) => {
        // datapoint에서 가장 가까운것을 찾는다
        let nextDataPoint = dataPoints.find((entry) => entry.x >= position.x)
    
        // undefined라면 마지막 데이터이다.
        if (!nextDataPoint) {
            nextDataPoint = dataPoints[lastItemIndex]
        }
    
        // 이전 데이터 위치를 찾는다.
        const previousDataPoint =
            dataPoints[dataPoints.indexOf(nextDataPoint) - 1]
    
        let currentDataPoint
    
        if (previousDataPoint) {
            // 2번과 3번과의 데이터를 가지고 데이터의 중간 위치를 구한다.
            const halfway =
                previousDataPoint.x +
                (nextDataPoint.x - previousDataPoint.x) / 2
            // 중간 위치보다 크다면, 2번 데이터; 없다면, 3번 데이터를 현재 데이터라고 가정한다.
            currentDataPoint =
                position.x >= halfway ? nextDataPoint : previousDataPoint
        } else {
            // 이전 데이터가 없다면, 첫번째 데이터라고 생각한다.
            currentDataPoint = nextDataPoint
        }
    
        const xChord = currentDataPoint.x
        const yChord = currentDataPoint.y
    
        // 선그리기
        $line.setAttribute('x1', xChord)
        $line.setAttribute('x2', xChord)
        $line.setAttribute('y1', 0)
        $line.setAttribute('y2', svgHeight)
    
        // 포인터
        $spot.setAttribute('cx', xChord)
        $spot.setAttribute('cy', xChord)
        $spot.setAttribute('r', sporRadios)
    }

4. D3 라이브러리를 활용하면, 간단하게 한줄로 바꿀수 있다.

  • D3란? interactive 데이터시각화(차트, 그래프)를 구현하기 위한 라이브러리
  • Jquery 같이 Dom에 접근해서 요소를 핸들링(생성, 및 속성 변경), 속성을 바꾸는 역할
  • 주요한 역할은 아래와 같이, 계산부분을 이용하여, 차트나 그래프를 그리기에 유용!
// mouse event
const handleMouseMove = (e) => {
    const [x, y] = d3.pointer(e)
    draw({ x, y })
}

// touch event
const handleTouchMove = (e) => {
    const touches = e.touches[0]
    const [x, y] = d3.pointer(touches)
    return { x, y }
}

const draw = ({ x }) => {
    // 해당 포인터의 위치 찾기
    const i = d3.bisectCenter(xCoords, xScale.invert(x))
}

5. 배포하기

나만 쓰기 아깝고... 누군가... 필요할것같고??... 자랑하고 싶어서...

  1. npm회원가입(이메일 확인! 꼭! 확인하자.. 이것때문에 10분 날림)

  2. package.json 수정

    • private으로 배포하는게 아니기 때문에, publishConfig를 public으로 설정
      • 만약 private로 사용하고 싶다면, 가격은 7$/month (등록 방법)
    {
      "name": "react-svg-spark-line",
      "version": "1.0.3",
      "description": "Simple SVG SparkLine React component",
      "main": "dist/index.js",
      "private": false,
      "module": "dist/index.js",
      "publishConfig": {
        "access": "public"
      },
      "bugs": {
        "url": "https://github.com/kode15333/sparklinechart/issues"
      },
      "homepage": "https://github.com/kode15333/sparklinechart#readme",
    }
  3. tsconfig.json 수정

    • include - Transpilers path
    • noEmit 은 분명히 true로 설정되있을땐데 무조건 false로 바꿔줘야한다. 안되면 트랜스파일링 된 파일이 나오지 않는다.
    {
      "compilerOptions": {
        "target": "es6",
        "module": "es6",
        "lib": [
          "dom",
          "es2015"
        ],
      },
      "include": [
        "./src/lib/*"
      ],
      "exclude": [
        "node_modules",
        "**/*.spec.ts",
        "./dist/**/*"
      ]
    }
  4. tsc 명령어로 트랜스컴파일!!

    • 두가지 형태로 나온다. 하나는 타입스크립트 전용 하나는 일반 리엑트 컴포넌트
    dist
    ├── SparkChart
    │   ├── index.d.ts
    │   ├── index.js
    │   ├── types.d.ts
    │   ├── types.js
    │   ├── useWindowSize.d.ts
    │   ├── useWindowSize.js
    │   ├── util.d.ts
    │   └── util.js
    ├── index.d.ts
    └── index.js
  5. npm 배포

  6. 결과물


© 2023, Customized by Joon