diff --git a/packages/sankey/index.d.ts b/packages/sankey/index.d.ts index 624def5cd..227966273 100644 --- a/packages/sankey/index.d.ts +++ b/packages/sankey/index.d.ts @@ -40,6 +40,8 @@ declare module '@nivo/sankey' { | 'color' | 'luminosity' + export type SankeySortFunction = (nodeA: SankeyDataNode, nodeB: SankeyDataNode) => number + export type SankeyProps = Partial<{ align: 'center' | 'justify' | 'left' | 'right' @@ -77,7 +79,7 @@ declare module '@nivo/sankey' { legends: LegendProps[] - sort?: (nodeA: SankeyDataNode, nodeB: SankeyDataNode) => number + sort: 'auto' | 'input' | 'ascending' | 'descending' | SankeySortFunction }> interface Dimensions { diff --git a/packages/sankey/package.json b/packages/sankey/package.json index 6abc5d46c..404b1b5ea 100644 --- a/packages/sankey/package.json +++ b/packages/sankey/package.json @@ -25,7 +25,7 @@ "dependencies": { "@nivo/core": "0.53.0", "@nivo/legends": "0.53.0", - "d3-sankey": "0.12.1", + "d3-sankey": "^0.12.1", "lodash": "^4.17.4", "react-motion": "^0.5.2", "recompose": "^0.30.0" diff --git a/packages/sankey/src/Sankey.js b/packages/sankey/src/Sankey.js index 2b4ee2b31..66d662f16 100644 --- a/packages/sankey/src/Sankey.js +++ b/packages/sankey/src/Sankey.js @@ -24,6 +24,7 @@ const Sankey = ({ data: _data, align, + sortFunction, margin, width, @@ -74,12 +75,10 @@ const Sankey = ({ tooltipFormat, legends, - - sort, }) => { const sankey = d3Sankey() - .nodeSort(sort) .nodeAlign(sankeyAlignmentFromProp(align)) + .nodeSort(sortFunction) .nodeWidth(nodeWidth) .nodePadding(nodePaddingY) .size([width, height]) diff --git a/packages/sankey/src/enhance.js b/packages/sankey/src/enhance.js index b65b26f58..f06d66b4f 100644 --- a/packages/sankey/src/enhance.js +++ b/packages/sankey/src/enhance.js @@ -38,5 +38,17 @@ export default Component => withPropsOnChange(['label', 'labelFormat'], ({ label, labelFormat }) => ({ getLabel: getLabelGenerator(label, labelFormat), })), + withPropsOnChange(['sort'], ({ sort }) => { + let sortFunction + if (sort === 'input') { + sortFunction = null + } else if (sort === 'ascending') { + sortFunction = (a, b) => a.value - b.value + } else if (sort === 'descending') { + sortFunction = (a, b) => b.value - a.value + } + + return { sortFunction } + }), pure )(Component) diff --git a/packages/sankey/src/props.js b/packages/sankey/src/props.js index 4ae8f481f..72c3b9f54 100644 --- a/packages/sankey/src/props.js +++ b/packages/sankey/src/props.js @@ -40,6 +40,10 @@ export const SankeyPropTypes = { }).isRequired, align: sankeyAlignmentPropType.isRequired, + sort: PropTypes.oneOfType([ + PropTypes.oneOf(['auto', 'input', 'ascending', 'descending']), + PropTypes.func, + ]).isRequired, nodeOpacity: PropTypes.number.isRequired, nodeHoverOpacity: PropTypes.number.isRequired, @@ -79,6 +83,7 @@ export const SankeyPropTypes = { export const SankeyDefaultProps = { align: 'center', + sort: 'auto', nodeOpacity: 0.75, nodeHoverOpacity: 1, diff --git a/website/src/components/charts/sankey/Sankey.js b/website/src/components/charts/sankey/Sankey.js index 797b634e2..31f97e04b 100644 --- a/website/src/components/charts/sankey/Sankey.js +++ b/website/src/components/charts/sankey/Sankey.js @@ -6,10 +6,10 @@ * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -import React, { Component } from 'react' +import React, { useState } from 'react' import { Link } from 'react-router-dom' import MediaQuery from 'react-responsive' -import { ResponsiveSankey, SankeyDefaultProps as defaults } from '@nivo/sankey' +import { ResponsiveSankey, SankeyDefaultProps } from '@nivo/sankey' import ChartHeader from '../../ChartHeader' import ChartTabs from '../../ChartTabs' import SankeyControls from './SankeyControls' @@ -20,193 +20,166 @@ import config from '../../../config' import nivoTheme from '../../../nivoTheme' import propsMapper from './propsMapper' -export default class Sankey extends Component { - state = { - settings: { - margin: { - top: 40, - right: 160, - bottom: 40, - left: 50, - }, +const initialSettings = { + margin: { + top: 40, + right: 160, + bottom: 40, + left: 50, + }, - align: 'justify', - colors: 'category10', + align: 'justify', + sort: 'auto', + colors: 'category10', - // nodes - nodeOpacity: 1, - nodeHoverOpacity: 1, - nodeWidth: 18, - nodePaddingX: 0, - nodePaddingY: 12, - nodeBorderWidth: 1, - nodeBorderColor: { - type: 'inherit:darker', - gamma: 0.8, - }, + nodeOpacity: 1, + nodeHoverOpacity: 1, + nodeWidth: 18, + nodePaddingX: 0, + nodePaddingY: 12, + nodeBorderWidth: 1, + nodeBorderColor: { + type: 'inherit:darker', + gamma: 0.8, + }, - // links - linkOpacity: 0.25, - linkHoverOpacity: 0.6, - linkHoverOthersOpacity: 0.1, - linkContract: 0, - linkBlendMode: 'multiply', - enableLinkGradient: true, + linkOpacity: 0.25, + linkHoverOpacity: 0.6, + linkHoverOthersOpacity: 0.1, + linkContract: 0, + linkBlendMode: 'multiply', + enableLinkGradient: true, - // labels - enableLabels: true, - labelPosition: 'outside', - labelOrientation: 'vertical', - labelPadding: 16, - labelTextColor: { - type: 'inherit:darker', - gamma: 1, - }, + enableLabels: true, + labelPosition: 'outside', + labelOrientation: 'vertical', + labelPadding: 16, + labelTextColor: { + type: 'inherit:darker', + gamma: 1, + }, - // motion - animate: true, - motionStiffness: 120, - motionDamping: 11, + animate: true, + motionStiffness: 120, + motionDamping: 11, - // interactivity - isInteractive: true, + isInteractive: true, - legends: [ + legends: [ + { + anchor: 'bottom-right', + direction: 'column', + translateX: 130, + itemWidth: 100, + itemHeight: 14, + itemDirection: 'right-to-left', + itemsSpacing: 2, + itemTextColor: '#999', + symbolSize: 14, + onClick: d => { + alert(JSON.stringify(d, null, ' ')) + }, + effects: [ { - anchor: 'bottom-right', - direction: 'column', - translateX: 130, - itemWidth: 100, - itemHeight: 14, - itemDirection: 'right-to-left', - itemsSpacing: 2, - itemTextColor: '#999', - symbolSize: 14, - onClick: d => { - alert(JSON.stringify(d, null, ' ')) + on: 'hover', + style: { + itemTextColor: '#000', }, - effects: [ - { - on: 'hover', - style: { - itemTextColor: '#000', - }, - }, - ], }, ], }, - } + ], +} - handleSettingsUpdate = settings => { - this.setState({ settings }) - } +const Sankey = ({ data, randomizeLinkValues }) => { + const [settings, setSettings] = useState(initialSettings) - render() { - const { data, randomizeLinkValues } = this.props - const { settings } = this.state + const mappedSettings = propsMapper(settings) - const mappedSettings = propsMapper(settings) + const code = generateCode('ResponsiveSankey', mappedSettings, { + pkg: '@nivo/sankey', + defaults: SankeyDefaultProps, + }) - const code = generateCode('ResponsiveSankey', mappedSettings, { - pkg: '@nivo/sankey', - defaults, - }) + const header = - const header = + const description = ( +
+

+ Computes a sankey diagram from nodes and links, built on top of{' '} + + d3-sankey + + . The responsive alternative of this component is ResponsiveSankey. +

+

+ Please be careful with the data you use for this chart as it does not support cyclic + dependencies. +
+ For example, something like A —> A or A —> B —> C —> A{' '} + will crash. +

+

+ This component is available in the{' '} + + nivo-api + + , see{' '} + + sample + {' '} + or try it using the API client. You can also see more + example usages in{' '} + + the storybook + + . +

+

+ See the dedicated guide on how to setup legends + for this component. +

+
+ ) - const description = ( -
-

- Computes a sankey diagram from nodes and links, uses{' '} - - d3-sankey - - , see{' '} - - this block - - . The responsive alternative of this component is ResponsiveSankey. -

-

- Please be careful with the data you use for this chart as it does not support - cyclic dependencies. -
- For example, something like A —> A or A —> B —> C —> A{' '} - will crash. -

-

- This component is available in the{' '} - - nivo-api - - , see{' '} - - sample - {' '} - or try it using the API client. You can also see - more example usages in{' '} - - the storybook - - . -

-

- See the dedicated guide on how to setup - legends for this component. -

+ return ( +
+
+ + {header} + {description} + + + + + +
- ) - - return ( -
-
- - {header} - {description} - - - - - - -
-
- - {header} - {description} - -
+
+ + {header} + {description} +
- ) - } +
+ ) } + +export default Sankey diff --git a/website/src/components/charts/sankey/SankeyAPI.js b/website/src/components/charts/sankey/SankeyAPI.js index dd8cab6e3..69549e8d2 100644 --- a/website/src/components/charts/sankey/SankeyAPI.js +++ b/website/src/components/charts/sankey/SankeyAPI.js @@ -34,7 +34,6 @@ export default class SankeyAPI extends Component { align: 'justify', colors: 'paired', - // nodes nodeOpacity: 0.75, nodeWidth: 18, nodePaddingX: 4, @@ -45,13 +44,12 @@ export default class SankeyAPI extends Component { gamma: 0.4, }, - // links linkOpacity: 0.15, - linkBlendMode: 'multiply', - enableLinkGradient: true, + // @todo: not yet supported by the API + // linkBlendMode: 'multiply', + // enableLinkGradient: true, linkContract: 0, - // labels enableLabels: true, labelPosition: 'inside', labelOrientation: 'vertical', diff --git a/website/src/components/charts/sankey/props.js b/website/src/components/charts/sankey/props.js index d0bdc56c4..8acd78eed 100644 --- a/website/src/components/charts/sankey/props.js +++ b/website/src/components/charts/sankey/props.js @@ -95,15 +95,16 @@ export default [ scopes: '*', description: ( - Defines node alignment method. Must be one of: {alignOptions}, see{' '} + Defines node alignment method. Must be one of: {alignOptions}, Please have a look at + the{' '} official d3 documentation - - . + {' '} + for further information. ), help: 'Node alignment method.', @@ -119,6 +120,56 @@ export default [ })), }, }, + { + key: 'sort', + scopes: ['Sankey'], + description: ( +
+ Defines node sorting method. Must be one of: +
    +
  • + 'auto' order of nodes within each + column is determined automatically by the layout. +
  • +
  • + 'input' order is fixed by the input. +
  • +
  • + 'ascending' node with lower values on + top. +
  • +
  • + 'descending' node with higher values on + top. +
  • +
  • + (nodeA, nodeB) => number +
  • +
+ Please have a look at the{' '} + + official d3 documentation + {' '} + for further information. +
+ ), + help: 'Node sorting method.', + type: '{string}', + required: false, + default: defaults.align, + controlType: 'choices', + controlGroup: 'Base', + controlOptions: { + choices: ['auto', 'input', 'ascending', 'descending'].map(key => ({ + label: key, + value: key, + })), + }, + }, { key: 'colors', scopes: '*', @@ -285,7 +336,7 @@ export default [ }, { key: 'linkBlendMode', - scopes: '*', + scopes: ['Sankey'], description: ( Defines CSS mix-blend-mode property for links, see{' '} @@ -330,7 +381,7 @@ export default [ }, { key: 'enableLinkGradient', - scopes: '*', + scopes: ['Sankey'], description: 'Enable/disable gradient from source/target nodes instead of plain color.', type: '{boolean}', required: false, diff --git a/yarn.lock b/yarn.lock index c11c11814..e44925e01 100644 --- a/yarn.lock +++ b/yarn.lock @@ -897,7 +897,7 @@ dependencies: regenerator-runtime "^0.12.0" -"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.2.0", "@babel/runtime@^7.3.1", "@babel/runtime@^7.3.4": +"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.2.0", "@babel/runtime@^7.3.4": version "7.4.2" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.4.2.tgz#f5ab6897320f16decd855eed70b705908a313fe8" integrity sha512-7Bl2rALb7HpvXFL7TETNzKSAeBVCPHELzc0C//9FCxN8nsiueWSJBqaF+2oIJScyILStASR/Cx5WMkXGYTiJFA== @@ -10809,7 +10809,7 @@ react-docgen@^3.0.0: node-dir "^0.1.10" recast "^0.16.0" -react-dom@^16.8.1, react-dom@^16.8.4: +react-dom@16.8.4, react-dom@^16.8.1, react-dom@^16.8.4: version "16.8.4" resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.8.4.tgz#1061a8e01a2b3b0c8160037441c3bf00a0e3bc48" integrity sha512-Ob2wK7XG2tUDt7ps7LtLzGYYB6DXMCLj0G5fO6WeEICtT4/HdpOi7W/xLzZnR6RCG1tYza60nMdqtxzA8FaPJQ== @@ -10970,13 +10970,6 @@ react-select@^2.3.0: react-input-autosize "^2.2.1" react-transition-group "^2.2.1" -react-spring@^8.0.18: - version "8.0.18" - resolved "https://registry.yarnpkg.com/react-spring/-/react-spring-8.0.18.tgz#f6fa09ab5acdeb37618c0e340d59830171b95c33" - integrity sha512-gb1Rtsnez2gxIHtbrbwQsAjE4ir/62m+gI+eT+1E0Uky5L1PbLccbywRIdIpK3W5jiNmU6OlEazVk0tfI8pZrA== - dependencies: - "@babel/runtime" "^7.3.1" - react-syntax-highlighter@^8.0.1: version "8.1.0" resolved "https://registry.yarnpkg.com/react-syntax-highlighter/-/react-syntax-highlighter-8.1.0.tgz#59103ff17a828a27ed7c8f035ae2558f09b6b78c" @@ -11016,7 +11009,7 @@ react-transition-group@^2.2.1: prop-types "^15.6.2" react-lifecycles-compat "^3.0.4" -react@^16.8.1, react@^16.8.4: +react@16.8.4, react@^16.8.1, react@^16.8.4: version "16.8.4" resolved "https://registry.yarnpkg.com/react/-/react-16.8.4.tgz#fdf7bd9ae53f03a9c4cd1a371432c206be1c4768" integrity sha512-0GQ6gFXfUH7aZcjGVymlPOASTuSjlQL4ZtVC5YKH+3JL6bBLCVO21DknzmaPlI90LN253ojj02nsapy+j7wIjg== @@ -13073,7 +13066,7 @@ unzip-response@^2.0.1: resolved "https://registry.yarnpkg.com/unzip-response/-/unzip-response-2.0.1.tgz#d2f0f737d16b0615e72a6935ed04214572d56f97" integrity sha1-0vD3N9FrBhXnKmk17QQhRXLVb5c= -upath@^1.1.0: +upath@1.1.0, upath@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/upath/-/upath-1.1.0.tgz#35256597e46a581db4793d0ce47fa9aebfc9fabd" integrity sha512-bzpH/oBhoS/QI/YtbkqCg6VEiPYjSZtrHQM6/QnJS6OL9pKUFLqb3aFh4Scvwm45+7iAgiMkLhSbaZxUqmrprw==