├── 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 |
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 | 
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 |
201 | );
202 | }
203 | }
204 |
--------------------------------------------------------------------------------