Skip to content

Commit

Permalink
feat(chord): improve Chord component
Browse files Browse the repository at this point in the history
  • Loading branch information
Raphaël Benitte committed Aug 31, 2017
1 parent 67f7876 commit 16af134
Show file tree
Hide file tree
Showing 8 changed files with 523 additions and 98 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@
"husky": "^0.14.3",
"jest": "^20.0.4",
"lint-staged": "^4.0.3",
"nivo-generators": "^0.8.0",
"nivo-generators": "0.9.0",
"prettier": "^1.5.3",
"react": "^15.6.1",
"react-dom": "^15.6.1",
Expand Down
197 changes: 103 additions & 94 deletions src/components/charts/chord/Chord.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,17 @@
* file that was distributed with this source code.
*/
import React from 'react'
import PropTypes from 'prop-types'
import compose from 'recompose/compose'
import defaultProps from 'recompose/defaultProps'
import pure from 'recompose/pure'
import { chord as d3Chord, ribbon as Ribbon } from 'd3-chord'
import { arc as Arc } from 'd3-shape'
import { rgb } from 'd3-color'
import { withTheme, withDimensions } from '../../../hocs'
import { getColorRange } from '../../../lib/colors'
import Container from '../Container'
import SvgWrapper from '../SvgWrapper'
import enhance from './enhance'
import { ChordPropTypes } from './props'
import ChordRibbons from './ChordRibbons'
import ChordArcs from './ChordArcs'

const Chord = ({
data,
matrix,
keys,

// dimensions
margin,
Expand All @@ -29,79 +26,126 @@ const Chord = ({
outerWidth,
outerHeight,

padAngle,
innerRadiusRatio,
innerRadiusOffset,
ribbonOpacity,
ribbonBorderWidth,
arcOpacity,
arcBorderWidth,

chord, // computed
arcGenerator, // computed
ribbonGenerator, // computed

// theming
theme,
colors,

// interactivity
isInteractive,
arcHoverOpacity,
arcHoverOthersOpacity,
ribbonHoverOpacity,
ribbonHoverOthersOpacity,

// motion
animate,
motionDamping,
motionStiffness,

currentArc,
setCurrentArc,
currentRibbon,
setCurrentRibbon,
}) => {
const centerX = width / 2
const centerY = height / 2

const color = getColorRange(colors)

const radius = Math.min(width, height) / 2
const arcInnerRadius = radius * innerRadiusRatio
const ribbonRadius = radius * (innerRadiusRatio - innerRadiusOffset)

const chord = d3Chord().padAngle(padAngle)

const arc = Arc().innerRadius(arcInnerRadius).outerRadius(radius)

const ribbon = Ribbon().radius(ribbonRadius)

const ribbons = chord(data)
const arcs = ribbons.groups
const colorById = keys.reduce((acc, key) => {
acc[key] = color(key)
return acc
}, {})

const ribbons = chord(matrix)
ribbons.forEach(ribbon => {
ribbon.source.id = keys[ribbon.source.index]
ribbon.source.color = colorById[ribbon.source.id]
ribbon.target.id = keys[ribbon.target.index]
ribbon.target.color = colorById[ribbon.target.id]
const ribbonKeys = [ribbon.source.id, ribbon.target.id]
ribbonKeys.sort()
ribbon.key = ribbonKeys.sort().join('.')
})

const arcs = ribbons.groups.map(arc => {
arc.key = arc.id = keys[arc.index]
arc.color = colorById[arc.id]
return arc
})

let getArcOpacity = () => arcOpacity
let getRibbonOpacity = () => ribbonOpacity
if (isInteractive) {
if (currentArc) {
getArcOpacity = arc => {
if (arc.id === currentArc.id) return arcHoverOpacity
return arcHoverOthersOpacity
}
getRibbonOpacity = ribbon => {
if (ribbon.source.id === currentArc.id) return ribbonHoverOpacity
return ribbonHoverOthersOpacity
}
} else if (currentRibbon) {
getArcOpacity = arc => {
if (arc.id === currentRibbon.source.id || arc.id === currentRibbon.target.id)
return arcHoverOpacity
return arcHoverOthersOpacity
}
getRibbonOpacity = ribbon => {
if (
ribbon.source.id === currentRibbon.source.id &&
ribbon.target.id === currentRibbon.target.id
)
return ribbonHoverOpacity
return ribbonHoverOthersOpacity
}
}
}

const motionProps = {
animate,
motionDamping,
motionStiffness,
}

return (
<Container isInteractive={isInteractive} theme={theme}>
{({ showTooltip, hideTooltip }) => {
return (
<SvgWrapper width={outerWidth} height={outerHeight} margin={margin}>
<g transform={`translate(${centerX}, ${centerY})`}>
<g>
{ribbons.map(d => {
let c = rgb(color(d.source.index))
c = rgb(c.r, c.g, c.b, ribbonOpacity)

return (
<path
key={`ribbon.${d.source.index}.${d.target.index}`}
className="nivo_chord_ribbon"
d={ribbon(d)}
fill={c}
stroke={c}
strokeWidth={ribbonBorderWidth}
/>
)
})}
</g>
<g>
{arcs.map(d => {
let c = rgb(color(d.index))
c = rgb(c.r, c.g, c.b, arcOpacity)

return (
<path
key={`arc.${d.index}`}
className="nivo_chord_arc"
d={arc(d)}
fill={c}
stroke={c}
strokeWidth={arcBorderWidth}
/>
)
})}
</g>
<ChordRibbons
ribbons={ribbons}
shapeGenerator={ribbonGenerator}
borderWidth={ribbonBorderWidth}
getOpacity={getRibbonOpacity}
setCurrent={setCurrentRibbon}
theme={theme}
showTooltip={showTooltip}
hideTooltip={hideTooltip}
{...motionProps}
/>
<ChordArcs
arcs={arcs}
shapeGenerator={arcGenerator}
borderWidth={arcBorderWidth}
getOpacity={getArcOpacity}
setCurrent={setCurrentArc}
theme={theme}
showTooltip={showTooltip}
hideTooltip={hideTooltip}
{...motionProps}
/>
</g>
</SvgWrapper>
)
Expand All @@ -110,41 +154,6 @@ const Chord = ({
)
}

Chord.propTypes = {
data: PropTypes.array.isRequired,

padAngle: PropTypes.number.isRequired,
innerRadiusRatio: PropTypes.number.isRequired,
innerRadiusOffset: PropTypes.number.isRequired,

ribbonOpacity: PropTypes.number.isRequired,
ribbonBorderWidth: PropTypes.number.isRequired,

// colors
colors: PropTypes.any.isRequired,

// interactivity
isInteractive: PropTypes.bool.isRequired,
}

export const ChordDefaultProps = {
padAngle: 0,
innerRadiusRatio: 0.9,
innerRadiusOffset: 0,

ribbonOpacity: 0.5,
ribbonBorderWidth: 1,

arcOpacity: 1,
arcBorderWidth: 1,

// colors
colors: 'nivo',

// interactivity
isInteractive: true,
}

const enhance = compose(defaultProps(ChordDefaultProps), withTheme(), withDimensions(), pure)
Chord.propTypes = ChordPropTypes

export default enhance(Chord)
130 changes: 130 additions & 0 deletions src/components/charts/chord/ChordArcs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
/*
* This file is part of the nivo project.
*
* Copyright 2016-present, Raphaël Benitte.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import React from 'react'
import PropTypes from 'prop-types'
import { TransitionMotion, spring } from 'react-motion'
import { colorMotionSpring, getInterpolatedColor } from '../../../lib/colors'
import BasicTooltip from '../../tooltip/BasicTooltip'

const ArcTooltip = ({ arc, theme }) =>
<BasicTooltip id={arc.id} value={arc.value} color={arc.color} enableChip={true} theme={theme} />

const ChordArcs = ({
arcs,
arcBorderWidth,
getOpacity,
shapeGenerator,
theme,
setCurrent,
showTooltip,
hideTooltip,

// motion
animate,
motionDamping,
motionStiffness,
}) => {
const commonProps = arc => {
const arcTooltip = <ArcTooltip arc={arc} theme={theme} />

return {
strokeWidth: arcBorderWidth,
onMouseEnter: e => {
setCurrent(arc)
showTooltip(arcTooltip, e)
},
onMouseMove: e => {
showTooltip(arcTooltip, e)
},
onMouseLeave: () => {
setCurrent(null)
hideTooltip()
},
}
}

if (animate !== true) {
return (
<g>
{arcs.map(arc => {
const opacity = getOpacity(arc)

return (
<path
key={arc.key}
d={shapeGenerator(arc)}
fill={arc.color}
fillOpacity={opacity}
stroke={arc.color}
strokeOpacity={opacity}
{...commonProps(arc)}
/>
)
})}
</g>
)
}

const springConfig = {
damping: motionDamping,
stiffness: motionStiffness,
}

return (
<TransitionMotion
styles={arcs.map(arc => {
return {
key: arc.key,
data: arc,
style: {
startAngle: spring(arc.startAngle, springConfig),
endAngle: spring(arc.endAngle, springConfig),
opacity: spring(getOpacity(arc), springConfig),
...colorMotionSpring(arc.color, springConfig),
},
}
})}
>
{interpolatedStyles =>
<g>
{interpolatedStyles.map(({ key, style, data: arc }) => {
const color = getInterpolatedColor(style)

return (
<path
key={key}
d={shapeGenerator({
startAngle: style.startAngle,
endAngle: style.endAngle,
})}
fill={color}
fillOpacity={style.opacity}
stroke={color}
strokeOpacity={style.opacity}
{...commonProps(arc)}
/>
)
})}
</g>}
</TransitionMotion>
)
}

ChordArcs.propTypes = {
arcs: PropTypes.array.isRequired,
shapeGenerator: PropTypes.func.isRequired,
borderWidth: PropTypes.number.isRequired,
getOpacity: PropTypes.func.isRequired,
setCurrent: PropTypes.func.isRequired,
theme: PropTypes.object.isRequired,
showTooltip: PropTypes.func.isRequired,
hideTooltip: PropTypes.func.isRequired,
}

export default ChordArcs
Loading

0 comments on commit 16af134

Please sign in to comment.