D3 및 React를 통한 농구 데이터 확보
73001 단어 reactd3datajavascript
2018-19시즌 로스앤젤레스 로스앤젤레스 로스앤젤레스의 득점 총수를 가시화하기 위해 도넛 튀김 그림을 제작한다.
데이터
우리가 데이터 시각화를 만들어야 하는 첫 번째 일은 데이터이다. 이것은 우연이 아니다.This well written article는 인터넷이 캡처한 일부 법률과 도덕적 결과를 설명했다.This repository 무료 공공 데이터 링크 제공.Dev.to 자체에는 데이터, 웹 캡처기, 시각화에 관한 많은 글이 있습니다.나의 2분의 1은 간단한 데이터 시각화 프로젝트에 좋은 오래된 크롬 개발 도구 자체가 데이터를 수집하고 만들 수 있다는 것이다.이 지나치게 간소화된 예를 봐라.
이름
나이.
르브라운 제임스
34
시안 윌리엄슨
십팔
조던
56
위 표에서 데이터를 마사지하려면 다음과 같이 하십시오.
c
(변수 이름)을 입력하고 Enter
를 누르면 새 그룹이 콘솔Store as Global Variable
를 선택합니다.콘솔에서 볼 수 있습니다 temp1
.copy
함수를 사용하여 임시 변수를 클립보드에 복사 - copy(temp1)
var a = document.querySelectorAll('tr') // 2
var b = Array.from(a).slice(1) // 3
var c = b.map(el => {
// 4
var name = el.children[0].innerText
var age = el.children[1].innerText
return { name, age }
})
c // 5
// right click array
copy(temp1) // 7
주의, 각 장면은 모두 다르다. 이 과정을 설명하는 데 도움을 주기 위해 우리는 이 예시를 간소화했다.그 밖에 상술한 모든 논리는 하나의 함수에 놓아 절차를 간소화할 수 있다.컨트롤러에 여러 줄 함수를 만들어서 새 줄을 만들 수 있다는 것을 기억하십시오.이 방법을 사용하면 JavaScript 101을 사용하여 웹을 수동으로 캡처할 수 있습니다.Shift+Enter
가지 말아야 할 곳에 가서 데이터를 수집하기 전에 반드시 사이트의 서비스 조항을 읽어야 한다.라운드 맵 생성하기
D3와 React를 함께 작업하는 것은 사실 결코 복잡하지 않다.일반적으로 필요한 것은 DOM의 입구점과 페이지를 불러올 때 시각화된 논리를 초기화하는 것입니다.우리의 예시 프로젝트를 시작하기 위해서, 우리는 설치를 희망한다willy-nilly
.첫 번째 단계는 새 프로젝트를 만드는 것입니다.내가 좋아하는 첫 번째 일은 목록을 지우고 create-react-app
과src
만 남기는 것이다.낡은 App.js
문장을 삭제하는 것을 잊지 마세요.어떤 코드를 작성하기 전에, 우리는 몇 개의 의존항을 찾아야 한다.
1 - D3 및 스타일 구성 요소를 다운로드합니다.
npm i d3 styled-components
2 - index.js
디렉토리에 새 파일 import
, 심지어는 whatever-you-want.js
을 생성합니다.예제에 사용된 데이터는 사용 가능합니다in this gist.
3 - D3+React+ 스타일 어셈블리라고도 하는 다양한 구성에 사용할 수 있는 기본 템플릿을 작성합니다.대부분의 개발자들처럼 나는 자신의 괴벽과 패턴을 가지고 있다.예를 들어 나는 data.js
검은색에 시달렸다. 그래서 나는 src
, 나는 글씨체#000000
등을 좋아한다. 만약 네가 이전에 갈고리를 사용한 적이 없다면, 빈 #333333
의존 수조의 Raleway
갈고리는 React 클래스 구성 요소의 useEffect
와 유사하다.번호의 주석은 곧 다가올 단계에 대응하고 이 단계의 코드를 삽입하는 곳이다.
import React, { useRef, useEffect, useState } from 'react'
import * as d3 from 'd3'
import styled, { createGlobalStyle } from 'styled-components'
import data from './data'
const width = 1000
const height = 600
const black = '#333333'
const title = 'My Data Visualization'
// 4
// 7
export const GlobalStyle = createGlobalStyle`
@import url('https://fonts.googleapis.com/css?family=Raleway:400,600&display=swap');
body {
font-family: 'Raleway', Arial, Helvetica, sans-serif;
color: ${black};
padding: 0;
margin: 0;
}
`
export const Container = styled.div`
display: grid;
grid-template-rows: 30px 1fr;
align-items: center;
.title {
font-size: 25px;
font-weight: 600;
padding-left: 20px;
}
`
export const Visualization = styled.div`
justify-self: center;
width: ${width}px;
height: ${height}px;
// 6
`
export default () => {
const visualization = useRef(null)
useEffect(() => {
var svg = d3
.select(visualization.current)
.append('svg')
.attr('width', width)
.attr('height', height)
// 5
// 8
}, [])
return (
<>
<GlobalStyle/>
<Container>
<div className='title'>{title}</div>
<Visualization ref={visualization} />
{/*10*/}
</Container>
<>
)
}
4. 우리는 우리의 튀김 도넛 그림을 위해 배색 방안과 차원을 세워야 한다.
우리 과자의 반경.
const radius = Math.min(width, height) / 2
로스앤젤레스의 색깔 테마를 사용해야만 의미가 있다.
var lakersColors = d3
.scaleLinear()
.domain([0, 1, 2, 3])
.range(['#7E1DAF', '#C08BDA', '#FEEBBD', '#FDBB21'])
D3[]
함수는 데이터를 케이크 슬라이스에 반영합니다.백그라운드에 componentDidMount
및 pie
등의 필드를 추가함으로써 이를 실현합니다.우리는 카드를 씻고 슬라이스하는 순서에 선택할 수 있는 startAngle
함수를 사용하고 있다.이것을 가지고 놀면 그것을 전달하고endAngle
, 심지어는 그것을 밖에 두어 다른 안배를 얻는다.마지막으로, 우리는 sort
함수를 사용하여 D3에 null
속성 분할 케이크 그림을 사용하도록 알려 줍니다.value
변수를 콘솔에 기록하여 D3 pie 함수가 데이터에 미치는 작용을 개념화하는 데 도움을 줍니다.
var pie = d3
.pie()
.sort((a, b) => {
return a.name.length - b.name.length
})
.value(d => d.points)(data)
현재 우리는 points
함수를 사용하여 원형 레이아웃을 만들어야 한다.변수pie
는 우리의 도넛 튀김 그림에 사용되고, arc
는 잠시 후에 라벨 안내서로 사용됩니다.arc
는 보조 함수이며 잠시 후에도 사용됩니다.
var arc = d3
.arc()
.outerRadius(radius * 0.7)
.innerRadius(radius * 0.4)
var outerArc = d3
.arc()
.outerRadius(radius * 0.9)
.innerRadius(radius * 0.9)
function getMidAngle(d) {
return d.startAngle + (d.endAngle - d.startAngle) / 2
}
5 구조가 자리를 잡으면 거의 화면에서 뭔가를 볼 수 있다.
다음 내용을 원본outerArc
변수 성명에 연결합니다.
.append('g')
.attr('transform', `translate(${width / 2}, ${height / 2})`)
지금 우리가 getMidAngle
D3에 피드백을 할 때 마술이 발생한다.
svg
.selectAll('slices')
.data(pie)
.enter()
.append('path')
.attr('d', arc)
.attr('fill', (d, i) => lakersColors(i % 4))
.attr('stroke', black)
.attr('stroke-width', 1)
다음으로, 우리는 모든 절편에서 최종적으로 라벨을 가리키는 선을 그려야 한다.이름이 좋은 svg
함수는 pie
좌표를 가진 수조를 되돌려줍니다. 이 수조는 centroid
절편 (이 예에서 [x,y]
의 중심점에 있습니다.마지막으로, 우리는 세 개의 좌표 진열로 구성된 수조를 되돌릴 것이다. 이 좌표 진열은 현재 화면에 표시된 각 선의 원점, 구부러진 점과 끝점에 대응한다.pie
회선의 끝부분을 가리키는 방향을 정하는 데 도움이 된다.
svg
.selectAll('lines')
.data(pie)
.enter()
.append('polyline')
.attr('stroke', black)
.attr('stroke-width', 1)
.style('fill', 'none')
.attr('points', d => {
var posA = arc.centroid(d)
var posB = outerArc.centroid(d)
var posC = outerArc.centroid(d)
var midAngle = getMidAngle(d)
posC[0] = radius * 0.95 * (midAngle < Math.PI ? 1 : -1)
return [posA, posB, posC]
})
지금 우리의 생산 라인은 라벨을 붙일 준비가 되었다.라벨이 도표의 어느 쪽에 나타나느냐에 따라 뒤집기d
와 arc
의 순서를 통해 대칭성을 높이면 라벨이 더 좋아 보인다.midAngle
함수는 원본 name
을 points
이라는 키로 이동합니다.pie
객체의 최상위 키는 data
기능에 사용되는 각도 측정 값을 포함합니다.
svg
.selectAll('labels')
.data(pie)
.enter()
.append('text')
.text(d => {
var midAngle = getMidAngle(d)
return midAngle < Math.PI
? `${d.data.name} - ${d.data.points}`
: `${d.data.points} - ${d.data.name}`
})
.attr('class', 'label')
.attr('transform', d => {
var pos = outerArc.centroid(d)
var midAngle = getMidAngle(d)
pos[0] = radius * 0.99 * (midAngle < Math.PI ? 1 : -1)
return `translate(${pos})`
})
.style('text-anchor', d => {
var midAngle = getMidAngle(d)
return midAngle < Math.PI ? 'start' : 'end'
})
6 - 어떤 스타일로 우리의 라벨을 수식하기 위해서, 우리는 data
스타일의 구성 요소에 몇 줄의 코드를 추가하기만 하면 된다.D3를 사용하여 Reactpie
갈고리에 getMidAngle
속성을 추가한 다음 스타일화된 구성 요소를 사용하여 이 클래스를 정의합니다. 통합 라이브러리의 체크 상자를 선택한 것 같습니다.
.label {
font-size: 12px;
font-weight: 600;
}
7 - 우리는 보기에는 괜찮은데 왜 맛을 조금 늘리지 않고 사용자에게 상호작용하는 느낌을 주지 않는가.우리는 D3의 Visualization
함수를 사용하여 득점 총수를 신속하게 얻을 수 있다.
var total = d3.sum(data, d => d.points)
class
함수는 useEffect
노드에 간단하게 추가되어 우리의 총수를 표시합니다.sum
의showTotal
style 속성은 텍스트를 도넛 구멍의 중심에 두어야 한다.text
기능은 잠시 후에 발휘될 것이다.페이지가 불러올 때 텍스트를 표시할 수 있도록 text-anchor
함수를 호출하고 있습니다.
function showTotal() {
svg
.append('text')
.text(`Total: ${total}`)
.attr('class', 'total')
.style('text-anchor', 'middle')
}
function hideTotal() {
svg.selectAll('.total').remove()
}
showTotal()
우리는 단계 6의 middle
클래스 옆에 hideTotal
클래스를 추가해야 한다.
.total {
font-size: 20px;
font-weight: 600;
}
9. 번호 평론 시스템은 이 점에서 좀 거칠지만, 만약 당신이 이 점을 해냈다면, 당신은 충분히 똑똑해서 따라갈 수 있습니다.다음은 이 함수showTotal
입니다.이것들은 우리가 모든 부분에 응용할 탐지기다.
function onMouseOver(d, i) {
hideTotal()
setPlayer(d.data)
d3.select(this)
.attr('fill', d3.rgb(lakersColors(i % 4)).brighter(0.5))
.attr('stroke-width', 2)
.attr('transform', 'scale(1.1)')
}
function onMouseOut(d, i) {
setPlayer(null)
showTotal()
d3.select(this)
.attr('fill', lakersColors(i % 4))
.attr('stroke-width', 1)
.attr('transform', 'scale(1)')
}
슬라이스가 멈추면 필획과 채우기가 강조되고, 살짝 확대하면 시원한 효과가 추가됩니다.구멍에 더 많은 정보가 있는 툴팁을 붙여넣을 수 있도록 전체 텍스트도 전환됩니다.우선 total
블록을 만들어야 합니다. 만약 그것이 없다면, React 응용 프로그램은 어떻게 될까요?
const [player, setPlayer] = useState(null)
예민한 관찰자들은 label
의 인용에 주의를 기울이고 무슨 일이 일어났는지 알고 싶어 할지도 모른다.다음 탐지기는 hideTotal
D3 체인의 끝에 연결되어야 합니다.
.attr('class', 'slice')
.on('mouseover', onMouseOver)
.on('mouseout', onMouseOut)
우리는 state
클래스에서 this
를 사용하기 때문에, slices
스타일 구성 요소의 다른 쌍선을 통해 그것을 제어할 수 있습니다.
.slice {
transition: transform 0.5s ease-in;
}
10 - 마우스가 각 슬라이스에 멈추면서 나타나는 transform
상태를 표시하기 위한 툴팁을 만들 수 있습니다.
{
player ? (
<Tooltip>
<div>
<span className='label'>Name: </span>
<span>{player.name}</span>
<br />
<span className='label'>Points: </span>
<span>{player.points}</span>
<br />
<span className='label'>Percent: </span>
<span>{Math.round((player.points / total) * 1000) / 10}%</span>
</div>
</Tooltip>
) : null
}
새로운 정보에 있어서 사용자는 현재 선수가 득점한 팀의 점수 백분율만 얻는다.그러나 집중된 위치와 동작을 결합시켜 좋은 효과와 좋은 상호작용 감각을 창조했다.만약 더 많은 정보를 표시할 수 있거나, 내가 더 똑똑하다면, 유사한 패턴을 더욱 효과적으로 사용할 수 있을 것이다.마지막으로 필요한 것은 slice
구성 요소입니다. 다른 스타일의 구성 요소와 함께 사용됩니다.
export const Tooltip = styled.div`
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: ${radius * 0.7}px;
height: ${radius * 0.7}px;
display: grid;
align-items: center;
justify-items: center;
border-radius: 50%;
margin-top: 10px;
font-size: 12px;
background: #ffffff;
.label {
font-weight: 600;
}
`
에이, 우리의 최종 코드는 아래와 같아야 한다.
import React, { useRef, useEffect, useState } from 'react'
import * as d3 from 'd3'
import data from './data'
import styled, { createGlobalStyle } from 'styled-components'
/**
* Constants
*/
const width = 1000
const height = 600
const radius = Math.min(width, height) / 2
const black = '#333333'
const title = 'Los Angeles Lakers Scoring 2018-19'
/**
* D3 Helpers
*/
// total points
var total = d3.sum(data, d => d.points)
// lakers colors
var lakersColors = d3
.scaleLinear()
.domain([0, 1, 2, 3])
.range(['#7E1DAF', '#C08BDA', '#FEEBBD', '#FDBB21'])
// pie transformation
var pie = d3
.pie()
.sort((a, b) => {
return a.name.length - b.name.length
})
.value(d => d.points)(data)
// inner arc used for pie chart
var arc = d3
.arc()
.outerRadius(radius * 0.7)
.innerRadius(radius * 0.4)
// outer arc used for labels
var outerArc = d3
.arc()
.outerRadius(radius * 0.9)
.innerRadius(radius * 0.9)
// midAngle helper function
function getMidAngle(d) {
return d.startAngle + (d.endAngle - d.startAngle) / 2
}
/**
* Global Style Sheet
*/
export const GlobalStyle = createGlobalStyle`
@import url('https://fonts.googleapis.com/css?family=Raleway:400,600&display=swap');
body {
font-family: 'Raleway', Arial, Helvetica, sans-serif;
color: ${black};
padding: 0;
margin: 0;
}
`
/**
* Styled Components
*/
export const Container = styled.div`
display: grid;
grid-template-rows: 30px 1fr;
align-items: center;
user-select: none;
.title {
font-size: 25px;
font-weight: 600;
padding-left: 20px;
}
`
export const Visualization = styled.div`
justify-self: center;
width: ${width}px;
height: ${height}px;
.slice {
transition: transform 0.5s ease-in;
}
.label {
font-size: 12px;
font-weight: 600;
}
.total {
font-size: 20px;
font-weight: 600;
}
`
export const Tooltip = styled.div`
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: ${radius * 0.7}px;
height: ${radius * 0.7}px;
display: grid;
align-items: center;
justify-items: center;
border-radius: 50%;
margin-top: 10px;
font-size: 12px;
background: #ffffff;
.label {
font-weight: 600;
}
`
export default () => {
const [player, setPlayer] = useState(null)
const visualization = useRef(null)
useEffect(() => {
var svg = d3
.select(visualization.current)
.append('svg')
.attr('width', width)
.attr('height', height)
.append('g')
.attr('transform', `translate(${width / 2}, ${height / 2})`)
svg
.selectAll('slices')
.data(pie)
.enter()
.append('path')
.attr('d', arc)
.attr('fill', (d, i) => lakersColors(i % 4))
.attr('stroke', black)
.attr('stroke-width', 1)
.attr('class', 'slice')
.on('mouseover', onMouseOver)
.on('mouseout', onMouseOut)
svg
.selectAll('lines')
.data(pie)
.enter()
.append('polyline')
.attr('stroke', black)
.attr('stroke-width', 1)
.style('fill', 'none')
.attr('points', d => {
var posA = arc.centroid(d)
var posB = outerArc.centroid(d)
var posC = outerArc.centroid(d)
var midAngle = getMidAngle(d)
posC[0] = radius * 0.95 * (midAngle < Math.PI ? 1 : -1)
return [posA, posB, posC]
})
svg
.selectAll('labels')
.data(pie)
.enter()
.append('text')
.text(d => {
var midAngle = getMidAngle(d)
return midAngle < Math.PI
? `${d.data.name} - ${d.data.points}`
: `${d.data.points} - ${d.data.name}`
})
.attr('class', 'label')
.attr('transform', d => {
var pos = outerArc.centroid(d)
var midAngle = getMidAngle(d)
pos[0] = radius * 0.99 * (midAngle < Math.PI ? 1 : -1)
return `translate(${pos})`
})
.style('text-anchor', d => {
var midAngle = getMidAngle(d)
return midAngle < Math.PI ? 'start' : 'end'
})
function showTotal() {
svg
.append('text')
.text(`Total: ${total}`)
.attr('class', 'total')
.style('text-anchor', 'middle')
}
function hideTotal() {
svg.selectAll('.total').remove()
}
function onMouseOver(d, i) {
hideTotal()
setPlayer(d.data)
d3.select(this)
.attr('fill', d3.rgb(lakersColors(i % 4)).brighter(0.5))
.attr('stroke-width', 2)
.attr('transform', 'scale(1.1)')
}
function onMouseOut(d, i) {
setPlayer(null)
showTotal()
d3.select(this)
.attr('fill', lakersColors(i % 4))
.attr('stroke-width', 1)
.attr('transform', 'scale(1)')
}
showTotal()
}, [])
return (
<>
<GlobalStyle />
<Container>
<div className='title'>{title}</div>
<Visualization ref={visualization} />
{player ? (
<Tooltip>
<div>
<span className='label'>Name: </span>
<span>{player.name}</span>
<br />
<span className='label'>Points: </span>
<span>{player.points}</span>
<br />
<span className='label'>Percent: </span>
<span>{Math.round((player.points / total) * 1000) / 10}%</span>
</div>
</Tooltip>
) : null}
</Container>
</>
)
}
NBA Player Salaries & Performance 2018-19 (Bubble Chart)
Inspiration for example Donut Chart
Reference
이 문제에 관하여(D3 및 React를 통한 농구 데이터 확보), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다
https://dev.to/benjaminadk/basketball-stats-through-d3-react-4m10
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념
(Collection and Share based on the CC Protocol.)
npm i d3 styled-components
import React, { useRef, useEffect, useState } from 'react'
import * as d3 from 'd3'
import styled, { createGlobalStyle } from 'styled-components'
import data from './data'
const width = 1000
const height = 600
const black = '#333333'
const title = 'My Data Visualization'
// 4
// 7
export const GlobalStyle = createGlobalStyle`
@import url('https://fonts.googleapis.com/css?family=Raleway:400,600&display=swap');
body {
font-family: 'Raleway', Arial, Helvetica, sans-serif;
color: ${black};
padding: 0;
margin: 0;
}
`
export const Container = styled.div`
display: grid;
grid-template-rows: 30px 1fr;
align-items: center;
.title {
font-size: 25px;
font-weight: 600;
padding-left: 20px;
}
`
export const Visualization = styled.div`
justify-self: center;
width: ${width}px;
height: ${height}px;
// 6
`
export default () => {
const visualization = useRef(null)
useEffect(() => {
var svg = d3
.select(visualization.current)
.append('svg')
.attr('width', width)
.attr('height', height)
// 5
// 8
}, [])
return (
<>
<GlobalStyle/>
<Container>
<div className='title'>{title}</div>
<Visualization ref={visualization} />
{/*10*/}
</Container>
<>
)
}
const radius = Math.min(width, height) / 2
var lakersColors = d3
.scaleLinear()
.domain([0, 1, 2, 3])
.range(['#7E1DAF', '#C08BDA', '#FEEBBD', '#FDBB21'])
var pie = d3
.pie()
.sort((a, b) => {
return a.name.length - b.name.length
})
.value(d => d.points)(data)
var arc = d3
.arc()
.outerRadius(radius * 0.7)
.innerRadius(radius * 0.4)
var outerArc = d3
.arc()
.outerRadius(radius * 0.9)
.innerRadius(radius * 0.9)
function getMidAngle(d) {
return d.startAngle + (d.endAngle - d.startAngle) / 2
}
.append('g')
.attr('transform', `translate(${width / 2}, ${height / 2})`)
svg
.selectAll('slices')
.data(pie)
.enter()
.append('path')
.attr('d', arc)
.attr('fill', (d, i) => lakersColors(i % 4))
.attr('stroke', black)
.attr('stroke-width', 1)
svg
.selectAll('lines')
.data(pie)
.enter()
.append('polyline')
.attr('stroke', black)
.attr('stroke-width', 1)
.style('fill', 'none')
.attr('points', d => {
var posA = arc.centroid(d)
var posB = outerArc.centroid(d)
var posC = outerArc.centroid(d)
var midAngle = getMidAngle(d)
posC[0] = radius * 0.95 * (midAngle < Math.PI ? 1 : -1)
return [posA, posB, posC]
})
svg
.selectAll('labels')
.data(pie)
.enter()
.append('text')
.text(d => {
var midAngle = getMidAngle(d)
return midAngle < Math.PI
? `${d.data.name} - ${d.data.points}`
: `${d.data.points} - ${d.data.name}`
})
.attr('class', 'label')
.attr('transform', d => {
var pos = outerArc.centroid(d)
var midAngle = getMidAngle(d)
pos[0] = radius * 0.99 * (midAngle < Math.PI ? 1 : -1)
return `translate(${pos})`
})
.style('text-anchor', d => {
var midAngle = getMidAngle(d)
return midAngle < Math.PI ? 'start' : 'end'
})
.label {
font-size: 12px;
font-weight: 600;
}
var total = d3.sum(data, d => d.points)
function showTotal() {
svg
.append('text')
.text(`Total: ${total}`)
.attr('class', 'total')
.style('text-anchor', 'middle')
}
function hideTotal() {
svg.selectAll('.total').remove()
}
showTotal()
.total {
font-size: 20px;
font-weight: 600;
}
function onMouseOver(d, i) {
hideTotal()
setPlayer(d.data)
d3.select(this)
.attr('fill', d3.rgb(lakersColors(i % 4)).brighter(0.5))
.attr('stroke-width', 2)
.attr('transform', 'scale(1.1)')
}
function onMouseOut(d, i) {
setPlayer(null)
showTotal()
d3.select(this)
.attr('fill', lakersColors(i % 4))
.attr('stroke-width', 1)
.attr('transform', 'scale(1)')
}
const [player, setPlayer] = useState(null)
.attr('class', 'slice')
.on('mouseover', onMouseOver)
.on('mouseout', onMouseOut)
.slice {
transition: transform 0.5s ease-in;
}
{
player ? (
<Tooltip>
<div>
<span className='label'>Name: </span>
<span>{player.name}</span>
<br />
<span className='label'>Points: </span>
<span>{player.points}</span>
<br />
<span className='label'>Percent: </span>
<span>{Math.round((player.points / total) * 1000) / 10}%</span>
</div>
</Tooltip>
) : null
}
export const Tooltip = styled.div`
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: ${radius * 0.7}px;
height: ${radius * 0.7}px;
display: grid;
align-items: center;
justify-items: center;
border-radius: 50%;
margin-top: 10px;
font-size: 12px;
background: #ffffff;
.label {
font-weight: 600;
}
`
import React, { useRef, useEffect, useState } from 'react'
import * as d3 from 'd3'
import data from './data'
import styled, { createGlobalStyle } from 'styled-components'
/**
* Constants
*/
const width = 1000
const height = 600
const radius = Math.min(width, height) / 2
const black = '#333333'
const title = 'Los Angeles Lakers Scoring 2018-19'
/**
* D3 Helpers
*/
// total points
var total = d3.sum(data, d => d.points)
// lakers colors
var lakersColors = d3
.scaleLinear()
.domain([0, 1, 2, 3])
.range(['#7E1DAF', '#C08BDA', '#FEEBBD', '#FDBB21'])
// pie transformation
var pie = d3
.pie()
.sort((a, b) => {
return a.name.length - b.name.length
})
.value(d => d.points)(data)
// inner arc used for pie chart
var arc = d3
.arc()
.outerRadius(radius * 0.7)
.innerRadius(radius * 0.4)
// outer arc used for labels
var outerArc = d3
.arc()
.outerRadius(radius * 0.9)
.innerRadius(radius * 0.9)
// midAngle helper function
function getMidAngle(d) {
return d.startAngle + (d.endAngle - d.startAngle) / 2
}
/**
* Global Style Sheet
*/
export const GlobalStyle = createGlobalStyle`
@import url('https://fonts.googleapis.com/css?family=Raleway:400,600&display=swap');
body {
font-family: 'Raleway', Arial, Helvetica, sans-serif;
color: ${black};
padding: 0;
margin: 0;
}
`
/**
* Styled Components
*/
export const Container = styled.div`
display: grid;
grid-template-rows: 30px 1fr;
align-items: center;
user-select: none;
.title {
font-size: 25px;
font-weight: 600;
padding-left: 20px;
}
`
export const Visualization = styled.div`
justify-self: center;
width: ${width}px;
height: ${height}px;
.slice {
transition: transform 0.5s ease-in;
}
.label {
font-size: 12px;
font-weight: 600;
}
.total {
font-size: 20px;
font-weight: 600;
}
`
export const Tooltip = styled.div`
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: ${radius * 0.7}px;
height: ${radius * 0.7}px;
display: grid;
align-items: center;
justify-items: center;
border-radius: 50%;
margin-top: 10px;
font-size: 12px;
background: #ffffff;
.label {
font-weight: 600;
}
`
export default () => {
const [player, setPlayer] = useState(null)
const visualization = useRef(null)
useEffect(() => {
var svg = d3
.select(visualization.current)
.append('svg')
.attr('width', width)
.attr('height', height)
.append('g')
.attr('transform', `translate(${width / 2}, ${height / 2})`)
svg
.selectAll('slices')
.data(pie)
.enter()
.append('path')
.attr('d', arc)
.attr('fill', (d, i) => lakersColors(i % 4))
.attr('stroke', black)
.attr('stroke-width', 1)
.attr('class', 'slice')
.on('mouseover', onMouseOver)
.on('mouseout', onMouseOut)
svg
.selectAll('lines')
.data(pie)
.enter()
.append('polyline')
.attr('stroke', black)
.attr('stroke-width', 1)
.style('fill', 'none')
.attr('points', d => {
var posA = arc.centroid(d)
var posB = outerArc.centroid(d)
var posC = outerArc.centroid(d)
var midAngle = getMidAngle(d)
posC[0] = radius * 0.95 * (midAngle < Math.PI ? 1 : -1)
return [posA, posB, posC]
})
svg
.selectAll('labels')
.data(pie)
.enter()
.append('text')
.text(d => {
var midAngle = getMidAngle(d)
return midAngle < Math.PI
? `${d.data.name} - ${d.data.points}`
: `${d.data.points} - ${d.data.name}`
})
.attr('class', 'label')
.attr('transform', d => {
var pos = outerArc.centroid(d)
var midAngle = getMidAngle(d)
pos[0] = radius * 0.99 * (midAngle < Math.PI ? 1 : -1)
return `translate(${pos})`
})
.style('text-anchor', d => {
var midAngle = getMidAngle(d)
return midAngle < Math.PI ? 'start' : 'end'
})
function showTotal() {
svg
.append('text')
.text(`Total: ${total}`)
.attr('class', 'total')
.style('text-anchor', 'middle')
}
function hideTotal() {
svg.selectAll('.total').remove()
}
function onMouseOver(d, i) {
hideTotal()
setPlayer(d.data)
d3.select(this)
.attr('fill', d3.rgb(lakersColors(i % 4)).brighter(0.5))
.attr('stroke-width', 2)
.attr('transform', 'scale(1.1)')
}
function onMouseOut(d, i) {
setPlayer(null)
showTotal()
d3.select(this)
.attr('fill', lakersColors(i % 4))
.attr('stroke-width', 1)
.attr('transform', 'scale(1)')
}
showTotal()
}, [])
return (
<>
<GlobalStyle />
<Container>
<div className='title'>{title}</div>
<Visualization ref={visualization} />
{player ? (
<Tooltip>
<div>
<span className='label'>Name: </span>
<span>{player.name}</span>
<br />
<span className='label'>Points: </span>
<span>{player.points}</span>
<br />
<span className='label'>Percent: </span>
<span>{Math.round((player.points / total) * 1000) / 10}%</span>
</div>
</Tooltip>
) : null}
</Container>
</>
)
}
Reference
이 문제에 관하여(D3 및 React를 통한 농구 데이터 확보), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/benjaminadk/basketball-stats-through-d3-react-4m10텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)