* 통계화면을 보여주기 위한 라이브러리 설치하기
{
"name": "learnreactnative",
"version": "0.0.1",
"private": true,
"scripts": {
"android": "react-native run-android",
"ios": "react-native run-ios",
"lint": "eslint .",
"start": "react-native start",
"test": "jest"
},
"dependencies": {
"@react-native-firebase/app": "^18.4.0",
"@react-native-firebase/auth": "^18.4.0",
"@react-native-firebase/firestore": "^18.4.0",
"@react-native-firebase/storage": "^18.4.0",
"@react-navigation/bottom-tabs": "^6.5.8",
"@react-navigation/native": "^6.1.7",
"@react-navigation/native-stack": "^6.9.13",
"chart.js": "^4.4.0",
"faker": "^6.6.6",
"moment": "^2.29.4",
"react": "18.2.0",
"react-chartjs-2": "^5.2.0",
"react-native": "0.72.4",
"react-native-gifted-charts": "^1.3.11",
"react-native-linear-gradient": "^2.8.3",
"react-native-safe-area-context": "^4.7.1",
"react-native-screens": "^3.24.0",
"react-native-svg": "^13.14.0",
"react-native-vector-icons": "^10.0.0"
},
"devDependencies": {
"@babel/core": "^7.20.0",
"@babel/preset-env": "^7.20.0",
"@babel/runtime": "^7.20.0",
"@react-native/eslint-config": "^0.72.2",
"@react-native/metro-config": "^0.72.11",
"@tsconfig/react-native": "^3.0.0",
"@types/react": "^18.0.24",
"@types/react-test-renderer": "^18.0.0",
"babel-jest": "^29.2.1",
"eslint": "^8.19.0",
"jest": "^29.2.1",
"metro-react-native-babel-preset": "0.76.8",
"prettier": "^2.4.1",
"react-test-renderer": "18.2.0",
"typescript": "4.8.4"
},
"engines": {
"node": ">=16"
}
}
"chart.js": "^4.4.0",
"faker": "^6.6.6",
"react-chartjs-2": "^5.2.0",
"react-native-gifted-charts": "^1.3.11", // 사용
"react-native-linear-gradient": "^2.8.3", // 사용
"react-native-svg": "^13.14.0",
해당 라이브러리를 추가로 설치하였다. 바 그래프를 위하여 사용할 라이브러리는 아래 링크를 참고하기 바란다. chart.js, faker, react-chartjs-2 는 현재 사용하지 않는 라이브러리다. 또한, react-native-linear-gradient 라이브러리는 react-native-gifted-charts 라이브러에서 사용되는 의존성 라이브러리다.
https://gifted-charts.web.app/
App 컴포넌트에서 아래와 같이 코드를 수정한다. 대쉬보드 화면에서 통계분석을 위하여 전체 할일목록을 사용할 것이므로 todos 속성으로 해당 화면에 데이터를 전달해준다.
<Tab.Screen name="DashBoard" children={(props) => <DashBoardSceen todos={todos}/>} options={{
title: '통계',
tabBarIcon: ({ color, size }) => <Icon name="dashboard" color={color} size={size}/>
}}/>
* 대쉬보드 화면 구현하기
import { max } from 'moment'
import React, { useRef } from 'react'
import { SafeAreaView, View, Text, StyleSheet, StatusBar } from 'react-native'
import { BarChart } from "react-native-gifted-charts"
function DashBoardSceen({navigation, todos}){
console.log("할일목록 - 통계: ", todos.length)
const groupedByCategory = todos.reduce((groupedByCategory, todo) => {
if(!groupedByCategory[todo.category]) groupedByCategory[todo.category] = 0
groupedByCategory[todo.category]++
return groupedByCategory
}, {})
console.log(groupedByCategory)
const groupedByStatus = todos.reduce((groupedByStatus, todo) => {
const propName = `${todo.isDone? "완료" : "진행중"}`
if(!groupedByStatus[propName]) groupedByStatus[propName] = 0
groupedByStatus[propName]++
return groupedByStatus
}, {})
console.log(groupedByStatus)
const data = []
let maxValue = -Infinity
for(let category in groupedByCategory){
if(maxValue < groupedByCategory[category]) maxValue = groupedByCategory[category]
data.push({value: groupedByCategory[category], frontColor: '#006DFF', gradientColor: '#009FFF', spacing: 6, label: category})
}
const noOfSections = 10
const yAxisLabelTexts = Array(noOfSections).fill(0).map((_, id) => {
console.log("아이디 -------", id)
let unit = id * maxValue/noOfSections
// let unit = id * maxValue/noOfSections * 1000
// let unit = id * maxValue/noOfSections * 1000000
if(unit > 1000000) unit = (unit / 1000000).toString() + 'M'
else if(unit > 1000) unit = (unit / 1000).toString() + 'K'
return unit
})
console.log(yAxisLabelTexts)
return (
<SafeAreaView style={styles.block}>
<StatusBar backgroundColor="#a8c8ffff"></StatusBar>
<View style={styles.graphBg}>
<View style={styles.itemBg}>
<View><Text style={styles.statusText}>진행중</Text></View><View style={styles.badge}><Text style={styles.badgeText}>{groupedByStatus["진행중"]}</Text></View>
</View>
<View style={styles.itemBg}>
<View><Text style={styles.statusText}>완료</Text></View><View style={styles.badge}><Text style={styles.badgeText}>{groupedByStatus["완료"]}</Text></View>
</View>
</View>
<View style={styles.graphBg}>
<Text style={{color: 'white', fontSize: 16, fontWeight: 'bold'}}>
카테고리별 할일목록 수
</Text>
<View style={{padding: 20, alignItems: 'center'}}>
<BarChart
isAnimated
data={data}
barWidth={30}
initialSpacing={10}
spacing={30}
barBorderRadius={7}
showGradient
yAxisThickness={0}
xAxisType={'dashed'}
xAxisColor={'lightgray'}
yAxisTextStyle={{color: 'lightgray'}}
stepValue={1}
maxValue={maxValue}
noOfSections={noOfSections}
yAxisLabelTexts={yAxisLabelTexts}
labelWidth={40}
xAxisLabelTextStyle={{color: 'lightgray', textAlign: 'center'}}
lineConfig={{
color: '#F29C6E',
thickness: 3,
curved: true,
hideDataPoints: true,
shiftY: 20,
initialSpacing: -30,
}}
/>
</View>
</View>
</SafeAreaView>
)
}
const styles = StyleSheet.create({
block: {
flex: 1
},
graphBg: {
margin: 10,
padding: 16,
borderRadius: 20,
backgroundColor: '#232B5D',
},
itemBg: {
margin: 10,
padding: 16,
borderRadius: 20,
backgroundColor: '#262d3d',
flexDirection: 'row',
justifyContent: 'flex-end',
alignItems: 'center'
},
statusText: {
color: 'lightgray',
fontWeight: 'bold',
fontSize: 20,
marginRight: 10
},
badge: {
backgroundColor: '#385499',
borderRadius: 50,
padding: 10,
width: 50, height: 50,
justifyContent: 'center',
alignItems: 'center'
},
badgeText: {
fontSize: 20,
color: 'lightgray'
}
})
export default DashBoardSceen
screens > DashBoardScreen.js 파일을 위와 같이 수정한다.
import { max } from 'moment'
왜 추가되었는지 잘 모르겠다. 아마 maxValue 변수를 추가할때 비주얼편집기에서 자동으로 추가한것 같다.
React, { useRef } from 'react'
useRef 도 현재 사용하지 않는데 왜 추가되었는지 모르겠다.
import { BarChart } from "react-native-gifted-charts"
바 그래프를 그리기 위하여 설치한 라이브러리에서 BarChart 컴포넌트를 임포트한다.
function DashBoardSceen({navigation, todos}){
// 중략
}
대쉬보드 화면에서 전체 할일목록이 필요하므로 todos 를 props 로 전달받는다.
const groupedByCategory = todos.reduce((groupedByCategory, todo) => {
if(!groupedByCategory[todo.category]) groupedByCategory[todo.category] = 0
groupedByCategory[todo.category]++
return groupedByCategory
}, {})
console.log(groupedByCategory)
할일목록을 카테고리별로 분류하기 위하여 위와 같이 작성한다. reduce 메서드는 배열을 하나의 값(현재는 객체)으로 만들어준다. 맨 처음에 groupedByCategory 는 빈 객체({})로 시작한다. reduce 는 todos 배열에서 각각의 할일정보를 추출한 다음 카테고리별로 분류한다. 분류에 대한 로직은 자바스크립트 배열 시간에 설명하였으므로 자세한 설명은 생략하도록 한다.
const groupedByStatus = todos.reduce((groupedByStatus, todo) => {
const propName = `${todo.isDone? "완료" : "진행중"}`
if(!groupedByStatus[propName]) groupedByStatus[propName] = 0
groupedByStatus[propName]++
return groupedByStatus
}, {})
console.log(groupedByStatus)
할일목록을 진행상태에 따라 분류하는 코드이다. 카테고리별 분류와 동일한 로직을 사용한다. 다만 todo 의 isDone 속성은 true 나 false 값이므로 프로퍼티 이름을 "완료" 나 "진행중" 으로 변경한 다음 분류를 수행한다.
const data = []
let maxValue = -Infinity
for(let category in groupedByCategory){
if(maxValue < groupedByCategory[category]) maxValue = groupedByCategory[category]
data.push({value: groupedByCategory[category], frontColor: '#006DFF', gradientColor: '#009FFF', spacing: 6, label: category})
}
카테고리별로 분류된 객체(groupedByCategory)는 라이브러리에 그대로 전달해주지 못한다. 해당 라이브러리에 데이터를 전달하기 위해서는 객체를 엘리먼트로 가지는 배열 형태가 되어야 한다. 정확한 데이터 형식은 라이브러리 문서를 참고하기 바란다. 그래서 빈 배열을 생성한 다음 반복문을 돌면서 객체를 추가한다. 객체의 value 속성은 바 그래프에서 보여줄 숫자(바 그래프의 길이)를 의미한다. spacing 은 바 그래프 사이의 간격이다. label 은 카테고리 이름이다. maxValue 는 바 그래프에서 바(bar)가 그래프 밖으로 삐져나오지 않도록 최대값을 바 그래프의 전체높이로 설정하기 위함이다.
const noOfSections = 10
noOfSections 는 바 그래프에서 행(줄)의 갯수이다.
const yAxisLabelTexts = Array(noOfSections).fill(0).map((_, id) => {
console.log("아이디 -------", id)
let unit = id * maxValue/noOfSections
// let unit = id * maxValue/noOfSections * 1000 // K 확인용 테스트 코드
// let unit = id * maxValue/noOfSections * 1000000 // M 확인용 테스트 코드
if(unit > 1000000) unit = (unit / 1000000).toString() + 'M'
else if(unit > 1000) unit = (unit / 1000).toString() + 'K'
return unit
})
console.log(yAxisLabelTexts)
yAxisLabelTexts 변수는 Y 축에 보여줄 라벨(행의 단위)의 리스트를 배열로 저장한다. 행의 갯수가 10개라면 라벨도 10개가 필요하므로 noOfSections 변수를 이용하여 0 으로 초기화된 10개의 배열을 생성한다. map 메서드를 이용하여 각 엘리먼트의 id 값을 조회하고, id 값에 maxValue/noOfSections 를 곱하여 라벨(단위)을 생성한다. 예를 들어 10개의 라벨이 필요하면 일단 id 값은 0 ~ 9 가 된다. 만약 전체 데이터에서 최대값(maxValue)이 100 이라면 바 그래프의 높이는 100 이 된다. 100 을 기준으로 행의 갯수가 10 이라면 한 행의 간격은 10 이 된다. 즉, maxValue / noOfSections 가 된다. 결과적으로 Y 축에 보여줄 단위는 0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 이므로 id 값(0 ~ 9)에 위에서 구한 간격을 곱해주면 된다.
단위(unit)는 문자열이고 toString 메서드를 이용하여 문자열로 변환해준다. unit 값이 1000000 을 넘어가면 1M 이므로 1000000 으로 나눈 다음 "M"을 붙여준다. unit 값이 1000 을 넘어가면 1K 이므로 1000으로 나눈 다음 "K"를 붙여준다.
<View style={styles.graphBg}>
<View style={styles.itemBg}>
<View><Text style={styles.statusText}>진행중</Text></View><View style={styles.badge}><Text style={styles.badgeText}>{groupedByStatus["진행중"]}</Text></View>
</View>
<View style={styles.itemBg}>
<View><Text style={styles.statusText}>완료</Text></View><View style={styles.badge}><Text style={styles.badgeText}>{groupedByStatus["완료"]}</Text></View>
</View>
</View>
<View style={styles.graphBg}>
<Text style={{color: 'white', fontSize: 16, fontWeight: 'bold'}}>
카테고리별 할일목록 수
</Text>
<View style={{padding: 20, alignItems: 'center'}}>
<BarChart
isAnimated
data={data}
barWidth={30}
initialSpacing={10}
spacing={30}
barBorderRadius={7}
showGradient
yAxisThickness={0}
xAxisType={'dashed'}
xAxisColor={'lightgray'}
yAxisTextStyle={{color: 'lightgray'}}
stepValue={1}
maxValue={maxValue}
noOfSections={noOfSections}
yAxisLabelTexts={yAxisLabelTexts}
labelWidth={40}
xAxisLabelTextStyle={{color: 'lightgray', textAlign: 'center'}}
lineConfig={{
color: '#F29C6E',
thickness: 3,
curved: true,
hideDataPoints: true,
shiftY: 20,
initialSpacing: -30,
}}
/>
</View>
통계 데이터를 화면에 보여주는 코드이다.
<View style={styles.graphBg}>
<View style={styles.itemBg}>
<View><Text style={styles.statusText}>진행중</Text></View><View style={styles.badge}><Text style={styles.badgeText}>{groupedByStatus["진행중"]}</Text></View>
</View>
<View style={styles.itemBg}>
<View><Text style={styles.statusText}>완료</Text></View><View style={styles.badge}><Text style={styles.badgeText}>{groupedByStatus["완료"]}</Text></View>
</View>
</View>
전체 할일목록에서 할일의 진행상황을 분류해서 화면에 보여준다.
<View style={styles.graphBg}>
<Text style={{color: 'white', fontSize: 16, fontWeight: 'bold'}}>
카테고리별 할일목록 수
</Text>
<View style={{padding: 20, alignItems: 'center'}}>
<BarChart
isAnimated // 애니메이션 적용
data={data} // 그래프에 사용되는 배열 데이터
barWidth={30} // 바 그래프 너비
initialSpacing={10}
spacing={30}
barBorderRadius={7} // 바 그래프 모서리
showGradient
yAxisThickness={0}
xAxisType={'dashed'}
xAxisColor={'lightgray'}
yAxisTextStyle={{color: 'lightgray'}}
stepValue={1}
maxValue={maxValue} // 바 그래프 최대값 설정
noOfSections={noOfSections} // 바 그래프 행의 갯수
yAxisLabelTexts={yAxisLabelTexts} // 바 그래프 Y축 라벨 리스트
labelWidth={40} // 라벨 간격
xAxisLabelTextStyle={{color: 'lightgray', textAlign: 'center'}} // 바 그래프 X축 라벨 스타일
lineConfig={{
color: '#F29C6E',
thickness: 3,
curved: true,
hideDataPoints: true,
shiftY: 20,
initialSpacing: -30,
}}
/>
</View>
</View>
라이브러리에서 임포트한 BarChart 컴포넌트를 이용하여 카테고리별 할일목록의 갯수를 화면에 바 그래프로 보여준다.
const styles = StyleSheet.create({
block: {
flex: 1
},
graphBg: { // 통계분석 블럭 배경화면
margin: 10,
padding: 16,
borderRadius: 20,
backgroundColor: '#232B5D',
},
itemBg: { // 진행상태가 보이는 부분의 배경
margin: 10,
padding: 16,
borderRadius: 20,
backgroundColor: '#262d3d',
flexDirection: 'row',
justifyContent: 'flex-end',
alignItems: 'center'
},
statusText: { // 진행상태 문자열 스타일
color: 'lightgray',
fontWeight: 'bold',
fontSize: 20,
marginRight: 10
},
badge: { // 각 진행상태에 대한 갯수를 표시하는 배지 스타일
backgroundColor: '#385499',
borderRadius: 50,
padding: 10,
width: 50, height: 50,
justifyContent: 'center',
alignItems: 'center'
},
badgeText: { // 배지 텍스트 스타일
fontSize: 20,
color: 'lightgray'
}
})
진행상태와 바 그래프에 대한 전체적인 스타일 코드이다.
'프로젝트 > 할일목록 앱 (RN)' 카테고리의 다른 글
10. 소셜로그인 - 구글 로그인 (0) | 2023.10.24 |
---|---|
9. 소셜로그인 기능 - 카카오 로그인 구현하기 (0) | 2023.10.19 |
7. 캘린더 화면 - 달력 보여주기 (0) | 2023.10.04 |
6. 할일목록 수정 및 삭제 기능 만들기 (0) | 2023.09.27 |
5. 할일목록 날짜별로 필터링해서 보여주기 & 할일목록 최신순 정렬해서 보여주기 (0) | 2023.09.25 |