Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DonutChart → Donut, DonutSlice → Donut.Slice #269

Merged
merged 16 commits into from
Sep 28, 2018
Merged
43 changes: 43 additions & 0 deletions pages/components/docs/Donut.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Donut
The Donut component is a circular chart that shows the relative share of commit status states for a pull request.

## The `data` prop
The `data` prop is the simplest way to define the share of states. It takes an object literal with states as keys and the number of statuses with that state as values. Slices are always rendered clockwise in descending order by size.

```.jsx
<Donut data={{error: 2, pending: 3, success: 5}} />
```

When using the `data` prop, the fill of each slice comes from the corresponding value in the theme's `colors.state` object. In other words, if `theme.colors.state.error = "red"`, then the `error` slice will get `fill="red"`. You can customize the slice colors by either passing a custom `theme` prop or using the `Donut.Slice` component described below.

## System props

Donut components get `space` system props. Read our [System Props](/system-props) doc page for a full list of available props.
shawnbot marked this conversation as resolved.
Show resolved Hide resolved

## Component props

| Prop name | Type | Description |
shawnbot marked this conversation as resolved.
Show resolved Hide resolved
| :- | :- | :- |
| data | Object | Use the keys `error`, `pending`, and `success` to set values used to generate slices in the chart |
| size | Number | Used to set the width and height of the component |

# Donut.Slice
If you need to customize the color of your slices, you can use the `Donut.Slice` component as a child of `Donut`.

```.jsx
<Donut>
<Donut.Slice value={1} fill="pink" />
<Donut.Slice value={1} fill="salmon" />
<Donut.Slice value={1} fill="tomato" />
</Donut>
```

## `Donut.Slice` component props

| Prop name | Type | Description |
| :- | :- | :- |
| state | String | The commit status state which this slice represents |
| value | Number | The number of statuses with this slice's state |
| fill | String | The fill color of the slice, which overrides the color determined by the `state` prop |

export const meta = {displayName: 'Donut'}
26 changes: 0 additions & 26 deletions pages/components/docs/DonutChart.md

This file was deleted.

2 changes: 1 addition & 1 deletion pages/components/docs/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export {meta as CircleBadge} from './CircleBadge.md'
export {meta as CircleOcticon} from './CircleOcticon.md'
export {meta as CounterLabel} from './CounterLabel.md'
export {meta as Details} from './Details.md'
export {meta as DonutChart} from './DonutChart.md'
export {meta as Donut} from './Donut.md'
export {meta as Dropdown} from './Dropdown.md'
export {meta as FilterList} from './FilterList.md'
export {meta as Flash} from './Flash.md'
Expand Down
80 changes: 80 additions & 0 deletions src/Donut.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import React from 'react'
import PropTypes from 'prop-types'
import {arc as Arc, pie as Pie} from 'd3-shape'
import {themeGet} from 'styled-system'
import {withSystemProps, withDefaultTheme} from './system-props'

const defaultColor = '#666'
const getStateColors = themeGet('colors.state', {})

function Donut(props) {
const {className, data, children = mapData(data), size} = props

const radius = size / 2
const innerRadius = radius - 6

const pie = Pie().value(child => child.props.value)

// coerce the children into an array
const childList = React.Children.toArray(children)
const arcData = pie(childList)
const arc = Arc()
.innerRadius(innerRadius)
.outerRadius(radius)

const slices = childList.map((child, i) => {
return React.cloneElement(child, {d: arc(arcData[i])})
})

return (
<svg width={size} height={size} className={className}>
<g transform={`translate(${radius},${radius})`}>{slices}</g>
</svg>
)
}

function mapData(data) {
return Object.keys(data).map(key => <Slice key={key} state={key} value={data[key]} />)
}

Donut.defaultProps = {
size: 30
}

Donut.propTypes = {
// require elements, not mixed content: <Slice>, <title>, etc.
children: PropTypes.oneOfType([PropTypes.element, PropTypes.arrayOf(PropTypes.element)]),
data: PropTypes.objectOf(PropTypes.number),
size: PropTypes.number
}

const Slice = withDefaultTheme(props => {
const {children, d, fill, state, value} = props
const stateColors = getStateColors(props)
const color = fill || stateColors[state] || stateColors.unknown || defaultColor
return (
<path d={d} fill={color} data-value={value}>
{children}
</path>
)
})

Slice.propTypes = {
// <title> is really the only thing that should be acceptable here
children: PropTypes.shape({type: 'title'}),
d: PropTypes.string,
fill: PropTypes.string,
state: PropTypes.string,
/* eslint-disable react/no-unused-prop-types */
theme: PropTypes.shape({
colors: PropTypes.shape({
state: PropTypes.objectOf(PropTypes.string)
})
}),
/* eslint-enable */
value: PropTypes.number
}

Donut.Slice = Slice

export default withSystemProps(Donut, ['space'])
shawnbot marked this conversation as resolved.
Show resolved Hide resolved
48 changes: 0 additions & 48 deletions src/DonutChart.js

This file was deleted.

36 changes: 0 additions & 36 deletions src/DonutSlice.js

This file was deleted.

85 changes: 85 additions & 0 deletions src/__tests__/Donut.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import React from 'react'
import Donut from '../Donut'
import theme, {colors} from '../theme'
import {render} from '../utils/testing'

const {state} = colors

describe('Donut', () => {
it('is a system component', () => {
expect(Donut.systemComponent).toEqual(true)
})

it('renders the data prop', () => {
const donut = render(<Donut data={{error: 1}} />)
expect(donut).toMatchSnapshot()

expect(donut.type).toEqual('svg')
expect(donut.props.width).toEqual(30)
expect(donut.props.height).toEqual(30)
expect(donut.children).toHaveLength(1)

const [g] = donut.children
expect(g.type).toEqual('g')
expect(g.children).toHaveLength(1)

const [slice] = g.children
expect(slice.type).toEqual('path')
// expect(slice.props.fill).toEqual(colors.state.error)
})

it('renders Donut.Slice children', () => {
const donut = render(
<Donut>
<Donut.Slice state="success" value={1} />
<Donut.Slice state="failure" value={1} />
</Donut>
)
expect(donut).toMatchSnapshot()
expect(donut.children).toHaveLength(1)
const slices = donut.children[0].children
expect(slices).toHaveLength(2)
expect(slices.map(slice => slice.type)).toEqual(['path', 'path'])
expect(slices[0].props.fill).toEqual(colors.state.success)
expect(slices[1].props.fill).toEqual(colors.state.failure)
})

it('renders a single Donut.Slice child', () => {
const donut = render(
<Donut>
<Donut.Slice state="success" value={1} />
</Donut>
)
expect(donut).toMatchSnapshot()
expect(donut.type).toEqual('svg')
})

it('respects margin utility prop', () => {
expect(render(<Donut m={4} data={{error: 1}} />)).toHaveStyleRule('margin', `${theme.space[4]}px`)
})

it('respects padding utility prop', () => {
expect(render(<Donut p={4} data={{error: 1}} />)).toHaveStyleRule('padding', `${theme.space[4]}px`)
})

describe('Donut.Slice', () => {
it('renders known states as colors', () => {
expect(render(<Donut.Slice state="error" />).props.fill).toEqual(state.error)
expect(render(<Donut.Slice state="pending" />).props.fill).toEqual(state.pending)
expect(render(<Donut.Slice state="success" />).props.fill).toEqual(state.success)
expect(render(<Donut.Slice state="unknown" />).props.fill).toEqual(state.unknown)
})

it('renders unknown states with theme.colors.state.unknown', () => {
expect(render(<Donut.Slice state="xyz" />).props.fill).toEqual(state.unknown)
})

it('renders the fallback color when no state color is found in the theme', () => {
expect(render(<Donut.Slice state="error" theme={{}} />).props.fill).toEqual('#666')
})

it('respects the fill attribute', () => {
expect(render(<Donut.Slice fill="pink" />).props.fill).toEqual('pink')
})
})
})
63 changes: 0 additions & 63 deletions src/__tests__/DonutChart.js

This file was deleted.

Loading