Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@
"@mdx-js/react": "^1.6.21",
"antd": "^3.23.4",
"clsx": "^1.1.1",
"d3": "^7.7.0",
"lodash": "^4.17.21",
"prop-types": "^15.7.2",
"react-custom-scrollbars": "^4.2.1",
Expand All @@ -173,6 +174,7 @@
"recharts": "1.8.5",
"regenerator-runtime": "^0.13.7",
"rollup": "^2.34.2",
"sass": "^1.56.2",
"styled-components": "^4.3.2",
"tslib": "^2.3.1"
},
Expand Down
67 changes: 67 additions & 0 deletions src/charts/pie/Legend.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import React from 'react'
import _ from 'lodash'
import styled from 'styled-components'

const WrapLegendBox = styled.div`
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 100%;
margin-top: 8px;
`

const WrapLegendItem = styled.div`
display: flex;

& + & {
margin-top: 4px;
}
`

const WrapKey = styled.div.attrs({
className: 'body_02_m',
})`
display: flex;
align-items: center;
width: 5rem;
color: #6c7a89;

&::before {
content: '';
display: inline-block;
width: 0.5rem;
height: 0.5rem;
margin-right: 6px;
border-radius: 50%;
background-color: ${({ color }) => color};
}
`

const WrapValue = styled.span.attrs({
className: 'subtitle_01',
})`
display: flex;
align-items: center;
justify-content: center;
width: 5rem;
color: #3b424a;
`

const Index = ({ data, colors }) => {
const totalCount = _.reduce(data, (acc, cur) => (acc += cur.value), 0)

return (
<WrapLegendBox id="WrapLegendBox">
{_.map(data, ({ key, value }, idx) => (
<WrapLegendItem key={`WrapLegendItem__${key}`}>
<WrapKey color={colors[idx]}>{key}</WrapKey>
<WrapValue>{value || 0}</WrapValue>
<WrapValue>{((value * 100) / totalCount).toFixed(2)}%</WrapValue>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

p3. legend 에 value를 노출 시킬 필요가 있을까요? 기본적으로 이름을 노출하고 필요하다면 legend 를 custom 할 수 있도록 옵션을 전달할 수 있으면 좋겠습니다.

</WrapLegendItem>
))}
</WrapLegendBox>
)
}

export default Index
29 changes: 29 additions & 0 deletions src/charts/pie/PieChart.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
### Usage

#### Props

| Props Name | Types | Default |
| :--------: | :---: | :-----: |

#### Basic PieChart

```js
import React, { useState } from 'react'
import PieChart from './PieChart'

const render = () => {
return (
<div style={{ width: 350, height: 350 }}>
<PieChart
data={[
{ key: 'Male', value: 230 },
{ key: 'Female', value: 450 },
]}
colors={['#5A88D8', ' #ee807c']}
/>
</div>
)
}

render()
```
216 changes: 216 additions & 0 deletions src/charts/pie/PieChart.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
import React, { useEffect } from 'react'
import _ from 'lodash'
import * as d3 from 'd3'
import styled from 'styled-components'

import Legend from './Legend'

interface Data {
key: string
value: number
}

interface PieStyleObj {
width?: number
height?: number
margin?: number
radius?: number
innerRadius?: number
hoverStorkeWidth?: number
hoverStrokeColor?: number
}

interface DurationObj {
init?: number
hover?: number
}

interface TooltipObj {
x?: number
y?: number
}

interface PieChartProps {
data?: Data[]
colors?: string[]
style?: PieStyleObj
duration?: DurationObj
tooltip?: TooltipObj
id?: string
}

const PieChart = ({
// props 논의 필요. 임의 지정.
data,
colors,
style,
duration,
tooltip,
id = _.uniqueId('pie_chart'),
}: PieChartProps) => {
const colorSizeDiff = _.size(data) - _.size(colors)
if (colorSizeDiff !== 0) {
_.forEach(_.range(colorSizeDiff + 1), () =>
colors.push(`#${Math.round(Math.random() * 0xffffff).toString(16)}`),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

p4. 랜덤색상 말고 고정적인 default color 가 있는게 더 좋지 않을까요?

)
}

useEffect(() => {
// selector
const chartBox = d3.select(`#${id}`)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

p5. select 코드는 useEffect 바깥으로 빼도 괜찮지 않을까요 ..?

const svg = chartBox.select('.svg')
const tooltipBox = chartBox.select('.tooltip-box')
const tooltipSircle = chartBox.select('.tooltip-sircle')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

p5. sircle 은 어떤 의미로 사용된건가요? circle 의 오타인가 하여 남깁니다.

const tooltipTextBox = chartBox.select('.tooltip-text-box')

// default value
const SVG_WIDTH = svg.node().getBoundingClientRect().width - 16
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

p5. 여기서 - 16 은 무엇을 의미할까요 ..?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

p4. 16은 어떤값인가요? 상수화할 수 없을까요?

const SVG_HEIGHT = svg.node().getBoundingClientRect().height
const MARGIN = 8
const INIT_DURATION_MS = 600
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

p5. 상수도 바깥으로 빼도 괜찮을 것 같습니다 ~

const HOVER_DURATION_MS = 300
const TOOLTIP_DISTANCE_X = 30
const TOOLTIP_DISTANCE_Y = 0
const HOVER_STROKE_WIDTH = 1.5
const HOVER_STROKE_COLOR = '#24292d'

const {
width: svgWidth = SVG_WIDTH,
height: svgHeight = SVG_HEIGHT,
margin = MARGIN,
radius = Math.min(svgWidth, svgHeight) / 2 - margin,
innerRadius = radius / 2,
hoverStorkeWidth = HOVER_STROKE_WIDTH,
hoverStrokeColor = HOVER_STROKE_COLOR,
} = style || {}

const {
init: initDuration = INIT_DURATION_MS,
hover: hoverDuration = HOVER_DURATION_MS,
} = duration || {}

const {
x: tooltipDistanceX = TOOLTIP_DISTANCE_X,
y: tooltipDistanceY = TOOLTIP_DISTANCE_Y,
} = tooltip || {}

// event fucntion
const mouseover = (e, { data, index }) => {
d3.select(e.target)
.transition()
.duration(hoverDuration)
.attr('stroke-width', hoverStorkeWidth)
.style('stroke', hoverStrokeColor)

tooltipBox.style('visibility', 'visible')
tooltipSircle.style('background-color', colors[index])
tooltipTextBox
.append('div')
.style('margin-right', '0.5rem')
.text(`${data.key} :`)
tooltipTextBox.append('div').text(`${data.value}`)
Comment on lines +110 to +111
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

p3. tooltip 내용을 커스텀할 수 있는 방법은 없을까요?

}

const mousemove = (e) => {
tooltipBox
.style('left', `${e.pageX + tooltipDistanceX}px`)
.style('top', `${e.pageY + tooltipDistanceY}px`)
}

const mouseleave = ({ target }) => {
d3.select(target).transition().duration(300).attr('stroke-width', 0)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

p5. durtaion(300)에서 300도 상수처리하면 좋을 것 같습니다 ~

tooltipBox.style('visibility', 'hidden')
tooltipTextBox.selectAll('*').remove()
}

// default chart method
const pie = d3
.pie()
.value((d) => d.value)
.sort(null)

const arc = d3.arc().outerRadius(radius).innerRadius(innerRadius)

const interpolate = d3.interpolate(pie.startAngle()(), pie.endAngle()())

const chart = svg
.attr('width', svgWidth)
.attr('height', svgHeight)
.append('g')
.attr('transform', `translate(${svgWidth / 2},${svgHeight / 2})`)

chart
.selectAll('arc')
.data(pie(data))
.enter()
.append('path')
.attr('d', arc)
.attr('fill', (d) => colors[d.index])
.on('mouseover', mouseover)
.on('mousemove', mousemove)
.on('mouseleave', mouseleave)
.transition()
.duration(initDuration)
.attrTween('d', (d) => {
const originalEnd = d.endAngle
return (t) => {
const currentAngle = interpolate(t)
if (currentAngle < d.startAngle) return ''
d.endAngle = Math.min(currentAngle, originalEnd)
return arc(d)
}
})
}, [JSON.stringify(data)])
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

p5. 다른 props(colors, style, duration, tooltip 등)은 안들어가도 될까요

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

p5. JSON.stringify(data)가 좋은 방법이 아니어서 이리저리 다르게 시도하고 있는데ㅜ 나중에는 대체되어야 할 것 같아요..
의존성에 이것저것 넣어버리게 되면 동작을 해야할 경우에도 그대로인 경우가 있어서 아예 의존성을 빼도 괜찮을 것 같기도 합니다..
아니면 아예 다 넣어버려야 할까요 🥲


return (
<WrapChartBox id={id}>
<WrapSvg className="svg" />
<Legend data={data} colors={colors} />
<WrapTooltip className="tooltip-box">
<WrapSircle className="tooltip-sircle" />
<WrapTextBox className="tooltip-text-box" />
</WrapTooltip>
</WrapChartBox>
)
}

const WrapChartBox = styled.div`
display: flex;
flex-direction: column;
width: 100%;
height: 100%;
`

const WrapSvg = styled.svg`
width: 100%;
height: 100%;
`

const WrapTooltip = styled.div`
visibility: hidden;
display: flex;
align-items: center;
position: absolute;
min-width: 5rem;
width: max-content;
padding: 12px;
background-color: rgba(255, 255, 255, 0.95);
box-shadow: 0 3px 24px 0 rgba(36, 41, 45, 0.24);
border-radius: 0.3rem;
opacity: 1;
`

const WrapSircle = styled.div`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

p2. WrapCircle로 수정해야할 것 같습니다 ~

width: 0.5rem;
height: 0.5rem;
margin-right: 8px;
border-radius: 50%;
`

const WrapTextBox = styled.div`
display: flex;
width: 100%;
font-size: 1rem;
`

export default PieChart
9 changes: 9 additions & 0 deletions styleguide.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,15 @@ module.exports = {
},
],
},
{
name: 'Chart',
sections: [
{
name: 'PieChart',
content: 'src/charts/pie/PieChart.md',
},
],
},
],
propsParser: (filePath, source, resolver, handlers) => {
const { ext } = path.parse(filePath)
Expand Down
Loading