Jun 개발노트

SparkLine Chart 만들기 (2)

2021-11-29

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. 결과물