├── src ├── index.js ├── utils.js ├── main.css ├── Svg.js ├── Groups.js ├── Ribbons.js └── ChordDiagram.js ├── .gitignore ├── nwb.config.js ├── .travis.yml ├── CONTRIBUTING.md ├── LICENSE ├── package.json ├── demo └── src │ └── index.js └── README.md /src/index.js: -------------------------------------------------------------------------------- 1 | export { default } from './ChordDiagram'; 2 | 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /coverage 2 | /demo/dist 3 | /es 4 | /lib 5 | /node_modules 6 | /umd 7 | npm-debug.log* 8 | .idea/ 9 | -------------------------------------------------------------------------------- /nwb.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | type: 'react-component', 3 | npm: { 4 | esModules: true, 5 | umd: { 6 | global: 'ReactChordDiagram', 7 | externals: { 8 | react: 'React' 9 | } 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/utils.js: -------------------------------------------------------------------------------- 1 | /* Determines what ribbons should be hidden while mousing over a group */ 2 | export const isHiddenRibbon = (mouseOverGroup, sourceIndex, targetIndex) => { 3 | 4 | return mouseOverGroup !== null ? (mouseOverGroup !== sourceIndex && mouseOverGroup !== targetIndex) : false; 5 | }; -------------------------------------------------------------------------------- /src/main.css: -------------------------------------------------------------------------------- 1 | 2 | .svg-container { 3 | display: inline-block; 4 | position: relative; 5 | width: 100%; 6 | padding-bottom: 100%; 7 | vertical-align: top; 8 | overflow: hidden; 9 | } 10 | 11 | .svg-content { 12 | display: inline-block; 13 | position: absolute; 14 | top: 0; 15 | left: 0; 16 | } -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | 3 | language: node_js 4 | node_js: 5 | - 6 6 | 7 | before_install: 8 | - npm install codecov.io coveralls 9 | 10 | after_success: 11 | - cat ./coverage/lcov.info | ./node_modules/codecov.io/bin/codecov.io.js 12 | - cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js 13 | 14 | branches: 15 | only: 16 | - master 17 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Prerequisites 2 | 3 | [Node.js](http://nodejs.org/) >= v4 must be installed. 4 | 5 | ## Installation 6 | 7 | - Running `npm install` in the components's root directory will install everything you need for development. 8 | 9 | ## Demo Development Server 10 | 11 | - `npm start` will run a development server with the component's demo app at [http://localhost:3000](http://localhost:3000) with hot module reloading. 12 | 13 | ## Running Tests 14 | 15 | - `npm test` will run the tests once. 16 | 17 | - `npm run test:coverage` will run the tests and produce a coverage report in `coverage/`. 18 | 19 | - `npm run test:watch` will run the tests on every change. 20 | 21 | ## Building 22 | 23 | - `npm run build` will build the component for publishing to npm and also bundle the demo app. 24 | 25 | - `npm run clean` will delete built resources. 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Grayson Langford 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/Svg.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | const Svg = ({ 5 | width, 6 | height, 7 | style, 8 | className, 9 | clearHover, 10 | children, 11 | resizeWithWindow, 12 | onClick, 13 | }) => ( 14 |
15 | 20 | 21 | { clearHover(); onClick && onClick(event) } } 25 | width={width} 26 | x={`-${width / 2}`} 27 | y={`-${height / 2}`} 28 | /> 29 | { children } 30 | 31 | 32 |
33 | ); 34 | 35 | Svg.propTypes = { 36 | width: PropTypes.number, 37 | height: PropTypes.number, 38 | style: PropTypes.object, 39 | children: PropTypes.arrayOf(PropTypes.node), 40 | resizeWithWindow: PropTypes.bool, 41 | onClick: PropTypes.func 42 | }; 43 | 44 | export default Svg; 45 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-chord-diagram", 3 | "version": "2.0.0", 4 | "description": "A React component for building D3 Chord Diagrams", 5 | "main": "lib/index.js", 6 | "module": "es/index.js", 7 | "files": [ 8 | "css", 9 | "es", 10 | "lib", 11 | "umd" 12 | ], 13 | "scripts": { 14 | "build": "nwb build-react-component --copy-files", 15 | "clean": "nwb clean-module && nwb clean-demo", 16 | "start": "nwb serve-react-demo", 17 | "test": "nwb test-react", 18 | "test:coverage": "nwb test-react --coverage", 19 | "test:watch": "nwb test-react --server" 20 | }, 21 | "dependencies": { 22 | "d3-array": "^3.1.1", 23 | "d3-chord": "^3.0.1", 24 | "d3-color": "^3.0.1", 25 | "d3-format": "^3.0.1", 26 | "d3-scale": "^4.0.2", 27 | "d3-selection": "^3.0.0", 28 | "d3-shape": "^3.0.1", 29 | "prop-types": "^15.7.2" 30 | }, 31 | "peerDependencies": { 32 | "react": "17.x" 33 | }, 34 | "devDependencies": { 35 | "nwb": "^0.25.2", 36 | "react": "^17.0.2", 37 | "react-dom": "^17.0.2" 38 | }, 39 | "author": "Grayson Langford", 40 | "homepage": "", 41 | "license": "MIT", 42 | "repository": { 43 | "type": "git", 44 | "url": "https://github.com/graysoncl/react-chord-diagram" 45 | }, 46 | "keywords": [ 47 | "react", 48 | "chord-diagram", 49 | "d3", 50 | "react-component" 51 | ] 52 | } 53 | -------------------------------------------------------------------------------- /demo/src/index.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import {render} from 'react-dom'; 3 | 4 | import ChordDiagram from '../../src'; 5 | 6 | const matrix = [ 7 | [11975, 5871, 8916, 2868], 8 | [ 1951, 10048, 2060, 6171], 9 | [ 8010, 16145, 8090, 8045], 10 | [ 1013, 990, 940, 6907], 11 | ]; 12 | 13 | class Demo extends Component { 14 | constructor(props) { 15 | super(props); 16 | 17 | this.svgClicked = this.svgClicked.bind(this); 18 | } 19 | 20 | /* Sample of getting X/Y from the svg */ 21 | svgClicked(evt) { 22 | const e = evt.target; 23 | const dim = e.getBoundingClientRect(); 24 | const x = evt.clientX - dim.left; 25 | const y = evt.clientY - dim.top; 26 | alert("x: "+x+" y:"+y); 27 | } 28 | 29 | render() { 30 | return ( 31 |
32 | alert('Clicked group: ' + idx)} 39 | ribbonOnClick={(idx) => alert('Clicked ribbon: ' + idx)} 40 | height={600} 41 | width={600} 42 | /> 43 | 58 |
59 | ) 60 | } 61 | } 62 | 63 | render(, document.querySelector('#demo')); 64 | -------------------------------------------------------------------------------- /src/Groups.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { rgb } from 'd3-color'; 4 | 5 | const getAngle = (group) => ((group.startAngle + group.endAngle) / 2); 6 | 7 | const Groups = ({ 8 | componentId, 9 | chords, 10 | color, 11 | arc, 12 | outerRadius, 13 | setMouseOverGroup, 14 | groupLabels, 15 | labelColors, 16 | disableHover, 17 | hoverPersist, 18 | setHoverPersist, 19 | onClick, 20 | }) => ( 21 | 22 | {chords.groups.map((group, groupIndex) => ( 23 | setMouseOverGroup(group.index) : null} 26 | onMouseOut={(!disableHover && !hoverPersist) ? () => setMouseOverGroup(null) : null} 27 | onClick={ () => { setHoverPersist(!hoverPersist); onClick && onClick(group.index) } } 28 | > 29 | 34 | 35 | Math.PI ? "rotate(180)" : ""}`} 38 | fill={labelColors.length === 1 ? labelColors[0] : labelColors[groupIndex]} 39 | style={{textAnchor: (group.startAngle + group.endAngle) / 2 > Math.PI ? "end" : null}} 40 | > 41 | {groupLabels[groupIndex]} 42 | 43 | 44 | ))} 45 | 46 | ); 47 | 48 | Groups.propTypes = { 49 | componentId: PropTypes.number.isRequired, 50 | chords: PropTypes.array.isRequired, 51 | color: PropTypes.func.isRequired, 52 | arc: PropTypes.func.isRequired, 53 | setMouseOverGroup: PropTypes.func.isRequired, 54 | groupLabels: PropTypes.array, 55 | labelColors: PropTypes.array, 56 | disableHover: PropTypes.bool, 57 | persistHoverOnClick: PropTypes.bool, 58 | onClick: PropTypes.func 59 | }; 60 | 61 | export default Groups; 62 | -------------------------------------------------------------------------------- /src/Ribbons.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { rgb } from 'd3-color'; 4 | 5 | import { isHiddenRibbon } from './utils'; 6 | 7 | const Ribbons = ({ 8 | chords, 9 | color, 10 | disableHover, 11 | ribbon, 12 | setMouseOverRibbon, 13 | mouseOverGroup, 14 | mouseOverRibbon, 15 | hoverPersist, 16 | setHoverPersist, 17 | onClick, 18 | strokeWidth, 19 | blurOnHover, 20 | ribbonOpacity, 21 | ribbonBlurOpacity, 22 | }) => ( 23 | 27 | {chords.map((chord, chordIndex) => { 28 | const hidden = isHiddenRibbon(mouseOverGroup, chord.source.index, chord.target.index) || 29 | isHiddenRibbon(mouseOverRibbon, chordIndex, null); 30 | 31 | const style = ( blurOnHover ? 32 | { fillOpacity: `${ hidden ? ribbonBlurOpacity : ribbonOpacity }` } : 33 | { display: `${hidden ? 'none': 'block'}`, fillOpacity: ribbonOpacity } 34 | ) 35 | 36 | return ( 37 | { setHoverPersist(!hoverPersist); onClick && onClick(chordIndex) } } 45 | onMouseOver={(!disableHover && !hoverPersist) ? () => setMouseOverRibbon(chordIndex) : null} 46 | onMouseOut={(!disableHover && !hoverPersist) ? () => setMouseOverRibbon(null) : null} 47 | /> 48 | ) 49 | })} 50 | 51 | ); 52 | 53 | Ribbons.propTypes = { 54 | chords: PropTypes.array.isRequired, 55 | color: PropTypes.func.isRequired, 56 | ribbon: PropTypes.func.isRequired, 57 | setMouseOverRibbon: PropTypes.func.isRequired, 58 | mouseOverGroup: PropTypes.number, 59 | mouseOverRibbon: PropTypes.number, 60 | onClick: PropTypes.func, 61 | strokeWidth: PropTypes.number, 62 | disableHover: PropTypes.bool, 63 | blurOnHover: PropTypes.bool, 64 | }; 65 | 66 | export default Ribbons; 67 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React Chord Diagram 2 | 3 | A React component for building [D3 Chord Diagrams](https://github.com/d3/d3-chord) 4 | 5 | ## Table of Contents 6 | 7 | * [Installation](#installation) 8 | * [Usage](#usage) 9 | * [Required Props](#required-props) 10 | * [Optional Props](#optional-props) 11 | 12 | ## Installation 13 | 14 | $ npm install react-chord-diagram 15 | 16 | ## Usage 17 | 18 | ```js 19 | import ChordDiagram from 'react-chord-diagram' 20 | 21 | const matrix = [ 22 | [11975, 5871, 8916, 2868], 23 | [1951, 10048, 2060, 6171], 24 | [8010, 16145, 8090, 8045], 25 | [1013, 990, 940, 6907] 26 | ]; 27 | 28 | 34 | ``` 35 | ![screenshot](https://image.ibb.co/hH5Kpk/screenshot.png "Circos Table Viewer") 36 | 37 | ## Required Props 38 | 39 | ### matrix 40 | 41 | - type: `array of arrays` 42 | 43 | The matrix to be visualized. See [D3 Chord](https://github.com/d3/d3-chord#chord). 44 | 45 | example: 46 | 47 | [ 48 | [11975, 5871, 8916, 2868], 49 | [ 1951, 10048, 2060, 6171], 50 | [ 8010, 16145, 8090, 8045], 51 | [ 1013, 990, 940, 6907], 52 | ] 53 | 54 | ### componentId 55 | 56 | - type: `number` 57 | 58 | A unique id for the component. 59 | 60 | ## Optional Props 61 | 62 | ### width 63 | 64 | - type: `number` 65 | 66 | Width of the diagram in pixels. 67 | 68 | ### height 69 | 70 | - type: `number` 71 | 72 | Height of the diagram in pixels. 73 | 74 | ### style 75 | 76 | - type: `object` 77 | 78 | Custom styles applied to the diagram's root div. 79 | 80 | example: 81 | 82 | { 83 | font: '10px sans-serif' 84 | } 85 | 86 | ### className 87 | 88 | - type: `string` 89 | 90 | Custom class name applied to the root svg. 91 | 92 | ### outerRadius 93 | 94 | - type: `number` 95 | 96 | Outer radius of the diagram in pixels. 97 | 98 | ### innerRadius 99 | 100 | - type: `number` 101 | 102 | Inner radius of the diagram in pixels. 103 | 104 | ### groupColors 105 | 106 | - type: `array` 107 | 108 | List of colors, one for each group. 109 | 110 | example: 111 | 112 | ["#000000", "#FFDD89", "#957244", "#F26223"] 113 | 114 | 115 | ### padAngle 116 | 117 | - type: `number` 118 | 119 | Specifies the percent of padding between arcs or groups. 120 | 121 | default: .05 122 | 123 | ### sortGroups 124 | 125 | - type: `function` 126 | 127 | A function that specifies how the groups should be sorted. See [chord.sortGroups](https://github.com/d3/d3-chord#chord_sortGroups). 128 | 129 | default: null 130 | 131 | ### sortSubGroups 132 | 133 | - type: `function` 134 | 135 | A function that specifies how subgroups should be sorted. See [chord.sortSubGroups](https://github.com/d3/d3-chord#chord_sortSubgroups). 136 | 137 | default: d3.descending 138 | 139 | ### sortChords 140 | 141 | - type: `function` 142 | 143 | A function that specifies how chords should be sorted. See [chord.sortChords](https://github.com/d3/d3-chord#chord_sortChords). 144 | 145 | default: d3.descending 146 | 147 | ### labelColors 148 | 149 | - type: `array` 150 | 151 | The color of each label in the diagram. 152 | 153 | default: #000000 154 | 155 | ### disableHover 156 | 157 | - type: `boolean` 158 | 159 | Whether to hide other ribbons while mousing over a particular group or ribbon. 160 | This overrides the individual group / ribbon hover settings. 161 | 162 | default: false 163 | 164 | ### disableGroupHover 165 | 166 | - type: `boolean` 167 | 168 | Whether to hide other ribbons while mousing over a particular group. 169 | 170 | default: false 171 | 172 | ### disableRibbonHover 173 | 174 | - type: `boolean` 175 | 176 | Whether to hide other ribbons while mousing over a particular ribbon. 177 | 178 | default: false 179 | 180 | ### blurOnHover 181 | 182 | - type: `boolean` 183 | 184 | Whether to blur other ribbons instead of hiding them on hover. 185 | 186 | default: false 187 | 188 | ### persistHoverOnClick 189 | 190 | - type: `boolean` 191 | 192 | If true, ribbons highlighted on hover will remain highlighted if you click on 193 | the element causing the hover. Click anywhere on the SVG to clear this state. 194 | 195 | default: false 196 | 197 | ### ribbonOpacity 198 | 199 | - type: `string` 200 | 201 | Default opacity value for ribbons. 202 | 203 | default: '0.67' 204 | 205 | ### ribbonBlurOpacity 206 | 207 | - type: `string` 208 | 209 | If `blurOnHover` is true, then set 'hidden' ribbons to this opacity instead of 210 | hiding them. 211 | 212 | default: '0.2' 213 | 214 | ### strokeWidth 215 | 216 | - type: `number` 217 | 218 | Will change the stroke width of the ribbons. 219 | 220 | default: 1 221 | 222 | ### resizeWithWindow 223 | 224 | - type: `boolean` 225 | 226 | Resize the svg when the window is resized. 227 | 228 | default: false 229 | 230 | ### groupOnClick 231 | 232 | - type: `function` 233 | 234 | A function that will happen when a group is clicked. Group index is passed to 235 | the function. 236 | 237 | default: null 238 | 239 | ### ribbonOnClick 240 | 241 | - type: `function` 242 | 243 | A function that will happen when a ribbon is clicked. Ribbon index is passed 244 | to the function. 245 | 246 | default: null 247 | 248 | ### svgOnClick 249 | 250 | - type: `function` 251 | 252 | A function that will happen when the background SVG is clicked. The `event` is passed 253 | to the function. 254 | 255 | default: null 256 | 257 | [build-badge]: https://img.shields.io/travis/user/repo/master.png?style=flat-square 258 | [build]: https://travis-ci.org/user/repo 259 | 260 | [npm-badge]: https://img.shields.io/npm/v/npm-package.png?style=flat-square 261 | [npm]: https://www.npmjs.org/package/npm-package 262 | 263 | [coveralls-badge]: https://img.shields.io/coveralls/user/repo/master.png?style=flat-square 264 | [coveralls]: https://coveralls.io/github/user/repo 265 | -------------------------------------------------------------------------------- /src/ChordDiagram.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { arc } from 'd3-shape'; 4 | import { ribbon, chord } from 'd3-chord'; 5 | import { scaleOrdinal } from 'd3-scale'; 6 | import { range, descending } from 'd3-array'; 7 | 8 | import Svg from './Svg'; 9 | import Groups from './Groups'; 10 | import Ribbons from './Ribbons'; 11 | 12 | import './main.css' 13 | 14 | export default class ChordDiagram extends Component { 15 | static propTypes = { 16 | matrix: PropTypes.array.isRequired, 17 | componentId: PropTypes.number.isRequired, 18 | width: PropTypes.number, 19 | height: PropTypes.number, 20 | style: PropTypes.object, 21 | className: PropTypes.string, 22 | outerRadius: PropTypes.number, 23 | innerRadius: PropTypes.number, 24 | groupLabels: PropTypes.array, 25 | groupColors: PropTypes.array, 26 | padAngle: PropTypes.number, 27 | sortGroups: PropTypes.func, 28 | sortSubgroups: PropTypes.func, 29 | sortChords: PropTypes.func, 30 | labelColors: PropTypes.array, 31 | disableHover: PropTypes.bool, 32 | disableGroupHover: PropTypes.bool, 33 | disableRibbonHover: PropTypes.bool, 34 | strokeWidth: PropTypes.number, 35 | resizeWithWindow: PropTypes.bool, 36 | groupOnClick: PropTypes.func, 37 | ribbonOnClick: PropTypes.func, 38 | svgOnClick: PropTypes.func, 39 | blurOnHover: PropTypes.bool, 40 | ribbonOpacity: PropTypes.string, 41 | ribbonHoverOpacity: PropTypes.string, 42 | persistHoverOnClick: PropTypes.bool, 43 | }; 44 | 45 | static defaultProps = { 46 | matrix: [], 47 | componentId: 1, 48 | width: 700, 49 | height: 700, 50 | style: {}, 51 | className: '', 52 | outerRadius: null, 53 | innerRadius: null, 54 | groupLabels: [], 55 | groupColors: [], 56 | groupOnClick: null, 57 | padAngle: 0.05, 58 | sortGroups: null, 59 | sortSubgroups: descending, 60 | sortChords: null, 61 | labelColors: ['#000000'], 62 | disableHover: false, 63 | disableGroupHover: false, 64 | disableRibbonHover: true, 65 | strokeWidth: 1, 66 | resizeWithWindow: false, 67 | ribbonOnClick: null, 68 | blurOnHover: false, 69 | ribbonOpacity: '0.67', 70 | ribbonHoverOpacity: '0.2', 71 | persistHoverOnClick: false, 72 | svgOnClick: null, 73 | }; 74 | 75 | constructor (props) { 76 | super(props); 77 | 78 | this.clearHover = this.clearHover.bind(this); 79 | this.setHoverPersist = this.setHoverPersist.bind(this); 80 | this.setMouseOverGroup = this.setMouseOverGroup.bind(this); 81 | this.setMouseOverRibbon = this.setMouseOverRibbon.bind(this); 82 | } 83 | 84 | state = { 85 | hoverPersist: false, 86 | mouseOverGroup: null, 87 | mouseOverRibbon: null, 88 | }; 89 | 90 | clearHover() { 91 | this.setState({ hoverPersist: false, mouseOverGroup: null, mouseOverRibbon: null }) 92 | } 93 | 94 | setHoverPersist (hoverPersist) { 95 | if (this.props.persistHoverOnClick) { 96 | this.setState({hoverPersist}); 97 | } 98 | } 99 | 100 | setMouseOverGroup (mouseOverGroup) { 101 | this.setState({mouseOverGroup}); 102 | } 103 | 104 | setMouseOverRibbon (mouseOverRibbon) { 105 | this.setState({mouseOverRibbon}); 106 | } 107 | 108 | render() { 109 | const { 110 | matrix, 111 | componentId, 112 | width, 113 | height, 114 | style, 115 | className, 116 | groupLabels, 117 | groupColors, 118 | groupOnClick, 119 | padAngle, 120 | sortGroups, 121 | sortSubgroups, 122 | sortChords, 123 | labelColors, 124 | disableHover, 125 | disableGroupHover, 126 | disableRibbonHover, 127 | strokeWidth, 128 | resizeWithWindow, 129 | ribbonOnClick, 130 | blurOnHover, 131 | ribbonOpacity, 132 | ribbonBlurOpacity, 133 | persistHoverOnClick, 134 | svgOnClick, 135 | } = this.props; 136 | 137 | const outerRadius = this.props.outerRadius || Math.min(width, height) * 0.5 - 40; 138 | const innerRadius = this.props.innerRadius || outerRadius - 30; 139 | 140 | const d3Chord = chord() 141 | .padAngle(padAngle) 142 | .sortGroups(sortGroups) 143 | .sortSubgroups(sortSubgroups) 144 | .sortChords(sortChords); 145 | 146 | const chords = d3Chord(matrix); 147 | 148 | const d3Arc = arc() 149 | .innerRadius(innerRadius) 150 | .outerRadius(outerRadius); 151 | 152 | const d3Ribbon = ribbon() 153 | .radius(innerRadius); 154 | 155 | const color = scaleOrdinal() 156 | .domain(range(groupColors.length)) 157 | .range(groupColors); 158 | 159 | return ( 160 | 169 | 183 | 184 | 200 | 201 | ); 202 | } 203 | } 204 | --------------------------------------------------------------------------------