Skip to content

Commit

Permalink
feat(sankey): add gradient & blend mode support for links
Browse files Browse the repository at this point in the history
  • Loading branch information
Raphaël Benitte authored and Raphaël Benitte committed May 30, 2018
1 parent cf62e33 commit 27d5605
Show file tree
Hide file tree
Showing 10 changed files with 152 additions and 32 deletions.
4 changes: 4 additions & 0 deletions packages/nivo-sankey/src/Sankey.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ const Sankey = ({
linkHoverOpacity,
linkHoverOthersOpacity,
linkContract,
linkBlendMode,
enableLinkGradient,
getLinkColor, // computed
setCurrentLink, // injected
currentLink, // injected
Expand Down Expand Up @@ -152,6 +154,8 @@ const Sankey = ({
linkOpacity={linkOpacity}
linkHoverOpacity={linkHoverOpacity}
linkHoverOthersOpacity={linkHoverOthersOpacity}
linkBlendMode={linkBlendMode}
enableLinkGradient={enableLinkGradient}
showTooltip={showTooltip}
hideTooltip={hideTooltip}
setCurrentLink={setCurrentLink}
Expand Down
6 changes: 3 additions & 3 deletions packages/nivo-sankey/src/SankeyLabels.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import React from 'react'
import React, { Fragment } from 'react'
import PropTypes from 'prop-types'
import pure from 'recompose/pure'
import { TransitionMotion, spring } from 'react-motion'
Expand Down Expand Up @@ -102,7 +102,7 @@ const SankeyLabels = ({
})}
>
{interpolatedStyles => (
<g>
<Fragment>
{interpolatedStyles.map(({ key, style, data }) => {
const color = getInterpolatedColor(style)

Expand All @@ -124,7 +124,7 @@ const SankeyLabels = ({
</text>
)
})}
</g>
</Fragment>
)}
</TransitionMotion>
)
Expand Down
15 changes: 12 additions & 3 deletions packages/nivo-sankey/src/SankeyLinks.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import React from 'react'
import React, { Fragment } from 'react'
import PropTypes from 'prop-types'
import pure from 'recompose/pure'
import { sankeyLinkHorizontal } from 'd3-sankey'
import { motionPropTypes, SmartMotion } from '@nivo/core'
import { blendModePropType } from './props'
import SankeyLinksItem from './SankeyLinksItem'

const getLinkPath = sankeyLinkHorizontal()
Expand All @@ -23,6 +24,8 @@ const SankeyLinks = ({
linkHoverOpacity,
linkHoverOthersOpacity,
linkContract,
linkBlendMode,
enableLinkGradient,

// motion
animate,
Expand Down Expand Up @@ -60,6 +63,8 @@ const SankeyLinks = ({
color={link.color}
opacity={getOpacity(link)}
contract={linkContract}
blendMode={linkBlendMode}
enableGradient={enableLinkGradient}
showTooltip={showTooltip}
hideTooltip={hideTooltip}
setCurrent={setCurrentLink}
Expand All @@ -79,7 +84,7 @@ const SankeyLinks = ({
}

return (
<g>
<Fragment>
{links.map(link => (
<SmartMotion
key={`${link.source.id}.${link.target.id}`}
Expand All @@ -95,6 +100,8 @@ const SankeyLinks = ({
<SankeyLinksItem
link={link}
{...style}
blendMode={linkBlendMode}
enableGradient={enableLinkGradient}
showTooltip={showTooltip}
hideTooltip={hideTooltip}
setCurrent={setCurrentLink}
Expand All @@ -106,7 +113,7 @@ const SankeyLinks = ({
)}
</SmartMotion>
))}
</g>
</Fragment>
)
}

Expand All @@ -131,6 +138,8 @@ SankeyLinks.propTypes = {
linkHoverOpacity: PropTypes.number.isRequired,
linkHoverOthersOpacity: PropTypes.number.isRequired,
linkContract: PropTypes.number.isRequired,
linkBlendMode: blendModePropType.isRequired,
enableLinkGradient: PropTypes.bool.isRequired,

theme: PropTypes.object.isRequired,
tooltip: PropTypes.func,
Expand Down
47 changes: 35 additions & 12 deletions packages/nivo-sankey/src/SankeyLinksItem.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,14 @@
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import React from 'react'
import React, { Fragment } from 'react'
import PropTypes from 'prop-types'
import compose from 'recompose/compose'
import withPropsOnChange from 'recompose/withPropsOnChange'
import withHandlers from 'recompose/withHandlers'
import pure from 'recompose/pure'
import { BasicTooltip, Chip } from '@nivo/core'
import { blendModePropType } from './props'

const tooltipStyles = {
container: {
Expand Down Expand Up @@ -61,35 +62,55 @@ const SankeyLinksItem = ({
color,
opacity,
contract,
blendMode,
enableGradient,

// interactivity
handleMouseEnter,
handleMouseMove,
handleMouseLeave,
onClick,

link,
}) => (
<path
fill="none"
d={path}
strokeWidth={Math.max(1, width - contract * 2)}
stroke={color}
strokeOpacity={opacity}
onMouseEnter={handleMouseEnter}
onMouseMove={handleMouseMove}
onMouseLeave={handleMouseLeave}
onClick={onClick}
/>
<Fragment>
{enableGradient && (
<linearGradient
id={`${link.source.id}.${link.target.id}`}
gradientUnits="userSpaceOnUse"
x1={link.source.x}
x2={link.target.x}
>
<stop offset="0%" stopColor={link.source.color} />
<stop offset="100%" stopColor={link.target.color} />
</linearGradient>
)}
<path
fill="none"
d={path}
strokeWidth={Math.max(1, width - contract * 2)}
stroke={enableGradient ? `url(#${link.source.id}.${link.target.id})` : color}
strokeOpacity={opacity}
onMouseEnter={handleMouseEnter}
onMouseMove={handleMouseMove}
onMouseLeave={handleMouseLeave}
onClick={onClick}
style={{ mixBlendMode: blendMode }}
/>
</Fragment>
)

SankeyLinksItem.propTypes = {
link: PropTypes.shape({
source: PropTypes.shape({
id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
label: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
color: PropTypes.string.isRequired,
}).isRequired,
target: PropTypes.shape({
id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
label: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
color: PropTypes.string.isRequired,
}).isRequired,
color: PropTypes.string.isRequired,
value: PropTypes.number.isRequired,
Expand All @@ -100,6 +121,8 @@ SankeyLinksItem.propTypes = {
color: PropTypes.string.isRequired,
opacity: PropTypes.number.isRequired,
contract: PropTypes.number.isRequired,
blendMode: blendModePropType.isRequired,
enableGradient: PropTypes.bool.isRequired,

theme: PropTypes.object.isRequired,

Expand Down
10 changes: 5 additions & 5 deletions packages/nivo-sankey/src/SankeyNodes.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import React from 'react'
import React, { Fragment } from 'react'
import PropTypes from 'prop-types'
import pure from 'recompose/pure'
import { TransitionMotion, spring } from 'react-motion'
Expand Down Expand Up @@ -48,7 +48,7 @@ const SankeyNodes = ({

if (!animate) {
return (
<g>
<Fragment>
{nodes.map(node => (
<SankeyNodesItem
key={node.id}
Expand All @@ -69,7 +69,7 @@ const SankeyNodes = ({
theme={theme}
/>
))}
</g>
</Fragment>
)
}

Expand All @@ -96,7 +96,7 @@ const SankeyNodes = ({
})}
>
{interpolatedStyles => (
<g>
<Fragment>
{interpolatedStyles.map(({ key, style, data: node }) => {
const color = getInterpolatedColor(style)

Expand All @@ -121,7 +121,7 @@ const SankeyNodes = ({
/>
)
})}
</g>
</Fragment>
)}
</TransitionMotion>
)
Expand Down
23 changes: 23 additions & 0 deletions packages/nivo-sankey/src/props.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,25 @@ export const sankeyAlignmentPropType = PropTypes.oneOf(sankeyAlignmentPropKeys)

export const sankeyAlignmentFromProp = prop => sankeyAlignmentPropMapping[prop]

export const blendModePropType = PropTypes.oneOf([
'normal',
'multiply',
'screen',
'overlay',
'darken',
'lighten',
'color-dodge',
'color-burn',
'hard-light',
'soft-light',
'difference',
'exclusion',
'hue',
'saturation',
'color',
'luminosity',
])

export const SankeyPropTypes = {
data: PropTypes.shape({
nodes: PropTypes.arrayOf(
Expand Down Expand Up @@ -56,6 +75,8 @@ export const SankeyPropTypes = {
linkHoverOpacity: PropTypes.number.isRequired,
linkHoverOthersOpacity: PropTypes.number.isRequired,
linkContract: PropTypes.number.isRequired,
linkBlendMode: blendModePropType.isRequired,
enableLinkGradient: PropTypes.bool.isRequired,

// labels
enableLabels: PropTypes.bool.isRequired,
Expand Down Expand Up @@ -98,6 +119,8 @@ export const SankeyDefaultProps = {
linkHoverOpacity: 0.6,
linkHoverOthersOpacity: 0.15,
linkContract: 0,
linkBlendMode: 'multiply',
enableLinkGradient: false,

// labels
enableLabels: true,
Expand Down
21 changes: 13 additions & 8 deletions website/src/components/charts/sankey/Sankey.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import React, { Component } from 'react'
import { Link } from 'react-router-dom'
import MediaQuery from 'react-responsive'
import { ResponsiveSankey } from '@nivo/sankey'
import { ResponsiveSankey, SankeyDefaultProps as defaults } from '@nivo/sankey'
import ChartHeader from '../../ChartHeader'
import ChartTabs from '../../ChartTabs'
import SankeyControls from './SankeyControls'
Expand All @@ -34,22 +34,24 @@ export default class Sankey extends Component {
colors: 'd320b',

// nodes
nodeOpacity: 0.75,
nodeOpacity: 1,
nodeHoverOpacity: 1,
nodeWidth: 18,
nodePaddingX: 4,
nodePaddingX: 0,
nodePaddingY: 12,
nodeBorderWidth: 0,
nodeBorderWidth: 1,
nodeBorderColor: {
type: 'inherit:darker',
gamma: 0.4,
gamma: 0.8,
},

// links
linkOpacity: 0.2,
linkOpacity: 0.25,
linkHoverOpacity: 0.6,
linkHoverOthersOpacity: 0.1,
linkContract: 0,
linkBlendMode: 'multiply',
enableLinkGradient: true,

// labels
enableLabels: true,
Expand All @@ -58,7 +60,7 @@ export default class Sankey extends Component {
labelPadding: 16,
labelTextColor: {
type: 'inherit:darker',
gamma: 0.8,
gamma: 1,
},

// motion
Expand Down Expand Up @@ -94,7 +96,10 @@ export default class Sankey extends Component {

const mappedSettings = propsMapper(settings)

const code = generateCode('ResponsiveSankey', mappedSettings, { pkg: '@nivo/sankey' })
const code = generateCode('ResponsiveSankey', mappedSettings, {
pkg: '@nivo/sankey',
defaults,
})

const header = <ChartHeader chartClass="Sankey" tags={['relational', 'flow', 'api']} />

Expand Down
2 changes: 2 additions & 0 deletions website/src/components/charts/sankey/SankeyAPI.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ export default class SankeyAPI extends Component {

// links
linkOpacity: 0.15,
linkBlendMode: 'multiply',
enableLinkGradient: true,
linkContract: 0,

// labels
Expand Down
2 changes: 1 addition & 1 deletion website/src/components/charts/sankey/SankeyPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import Helmet from 'react-helmet'
import random from 'lodash/random'
import { generateSankeyData } from '@nivo/generators'

const generateData = () => generateSankeyData({ nodeCount: 9, maxIterations: 2 })
const generateData = () => generateSankeyData({ nodeCount: 13, maxIterations: 2 })

export default class SankeyPage extends Component {
state = {
Expand Down
Loading

0 comments on commit 27d5605

Please sign in to comment.