Skip to content

Commit

Permalink
Implements nodeSvgShape prop (#31)
Browse files Browse the repository at this point in the history
Add docs for nodeSvgShape prop
  • Loading branch information
bkrem committed Oct 6, 2017
1 parent 2b14a4f commit 4c8d520
Show file tree
Hide file tree
Showing 4 changed files with 104 additions and 29 deletions.
64 changes: 47 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ React D3 Tree is a [React](http://facebook.github.io/react/) component that lets
- [Installation](#installation)
- [Usage](#usage)
- [Props](#props)
- [Node shapes](#node-shapes)
- [Styling](#styling)
- [External data sources](#external-data-sources)

Expand Down Expand Up @@ -78,24 +79,53 @@ class MyComponent extends React.Component {


## Props
| Property | Type | Options | Required? | Default | Description |
|:---------------------|:----------------|:--------------------------------------|:----------|:--------------------------------|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `data` | `array` | | required | `undefined` | Single-element array containing hierarchical object (see `myTreeData` above). <br /> Contains (at least) `name` and `parent` keys. |
| `onClick` | `func` | | | `undefined` | Callback function to be called whenever a node is clicked. <br /><br /> The clicked node's data object is passed to the callback function as the first parameter. |
| `orientation` | `string` (enum) | `horizontal` `vertical` | | `horizontal` | `horizontal` - Tree expands left-to-right. <br /><br /> `vertical` - Tree expands top-to-bottom. |
| `translate` | `object` | | | `{x: 0, y: 0}` | Translates the graph along the x/y axis by the specified amount of pixels (avoids the graph being stuck in the top left canvas corner). |
| `pathFunc` | `string` (enum) | `diagonal` `elbow` `straight` | | `diagonal` | `diagonal` - Renders smooth, curved edges between parent-child nodes. <br /><br /> `elbow` - Renders sharp edges at right angles between parent-child nodes. <br /><br /> `straight` - Renders straight lines between parent-child nodes. |
| `collapsible` | `bool` | | | `true` | Toggles ability to collapse/expand the tree's nodes by clicking them. |
| `initialDepth` | `number` | `0..n` | | `undefined` | Sets the maximum node depth to which the tree is expanded on its initial render. <br /> Tree renders to full depth if prop is omitted. |
| `depthFactor` | `number` | `-n..0..n` | | `undefined` | Ensures the tree takes up a fixed amount of space (`node.y = node.depth * depthFactor`), regardless of tree depth. <br /> **TIP**: Negative values invert the tree's direction. |
| `zoomable` | `bool` | | | `true` | Toggles ability to zoom in/out on the Tree by scaling it according to `props.scaleExtent`. |
| `scaleExtent` | `object` | `{min: 0..n, max: 0..n}` | | `{min: 0.1, max: 1}` | Sets the minimum/maximum extent to which the tree can be scaled if `props.zoomable` is true. |
| `nodeSize` | `object` | `{x: 0..n, y: 0..n}` | | `{x: 140, y: 140}` | Sets a fixed size for each node. <br /><br /> This does not affect node circle sizes, circle sizes are handled by the `circleRadius` prop. |
| `separation` | `object` | `{siblings: 0..n, nonSiblings: 0..n}` | | `{siblings: 1, nonSiblings: 2}` | Sets separation between neighbouring nodes, differentiating between siblings (same parent) and non-siblings. |
| `circleRadius` | `number` | `0..n` | | `10` | Sets the radius of each node's `<circle>` element. |
| `transitionDuration` | `number` | `0..n` | | `500` | Sets the animation duration (in ms) of each expansion/collapse of a tree node. <br /><br /> Set this to `0` to deactivate animations completely. |
| `styles` | `object` | see [Styling](#styling) | | `Node`/`Link` CSS files | Overrides and/or enhances the tree's default styling. |
| Property | Type | Options | Required? | Default | Description |
|:------------------------|:----------------|:--------------------------------------|:----------|:---------------------------------------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `data` | `array` | | required | `undefined` | Single-element array containing hierarchical object (see `myTreeData` above). <br /> Contains (at least) `name` and `parent` keys. |
| `nodeSvgShape` | `object` | see [Node shapes](#node-shapes) | | `{shape: 'circle', shapeProps: r: 10}` | Sets a specific SVG shape element + shapeProps to be used for each node. |
| `onClick` | `func` | | | `undefined` | Callback function to be called whenever a node is clicked. <br /><br /> The clicked node's data object is passed to the callback function as the first parameter. |
| `orientation` | `string` (enum) | `horizontal` `vertical` | | `horizontal` | `horizontal` - Tree expands left-to-right. <br /><br /> `vertical` - Tree expands top-to-bottom. |
| `translate` | `object` | | | `{x: 0, y: 0}` | Translates the graph along the x/y axis by the specified amount of pixels (avoids the graph being stuck in the top left canvas corner). |
| `pathFunc` | `string` (enum) | `diagonal` `elbow` `straight` | | `diagonal` | `diagonal` - Renders smooth, curved edges between parent-child nodes. <br /><br /> `elbow` - Renders sharp edges at right angles between parent-child nodes. <br /><br /> `straight` - Renders straight lines between parent-child nodes. |
| `collapsible` | `bool` | | | `true` | Toggles ability to collapse/expand the tree's nodes by clicking them. |
| `initialDepth` | `number` | `0..n` | | `undefined` | Sets the maximum node depth to which the tree is expanded on its initial render. <br /> Tree renders to full depth if prop is omitted. |
| `depthFactor` | `number` | `-n..0..n` | | `undefined` | Ensures the tree takes up a fixed amount of space (`node.y = node.depth * depthFactor`), regardless of tree depth. <br /> **TIP**: Negative values invert the tree's direction. |
| `zoomable` | `bool` | | | `true` | Toggles ability to zoom in/out on the Tree by scaling it according to `props.scaleExtent`. |
| `scaleExtent` | `object` | `{min: 0..n, max: 0..n}` | | `{min: 0.1, max: 1}` | Sets the minimum/maximum extent to which the tree can be scaled if `props.zoomable` is true. |
| `nodeSize` | `object` | `{x: 0..n, y: 0..n}` | | `{x: 140, y: 140}` | Sets a fixed size for each node. <br /><br /> This does not affect node circle sizes, circle sizes are handled by the `circleRadius` prop. |
| `separation` | `object` | `{siblings: 0..n, nonSiblings: 0..n}` | | `{siblings: 1, nonSiblings: 2}` | Sets separation between neighbouring nodes, differentiating between siblings (same parent) and non-siblings. |
| `transitionDuration` | `number` | `0..n` | | `500` | Sets the animation duration (in ms) of each expansion/collapse of a tree node. <br /><br /> Set this to `0` to deactivate animations completely. |
| `styles` | `object` | see [Styling](#styling) | | `Node`/`Link` CSS files | Overrides and/or enhances the tree's default styling. |
| `circleRadius` (legacy) | `number` | `0..n` | | `undefined` | Sets the radius of each node's `<circle>` element.<br /><br /> **Will be deprecated in v2, please use `nodeSvgShape` instead.** |


## Node shapes
The `nodeSvgShape` prop allows specifying any [SVG shape primitive](https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Basic_Shapes) to describe how the tree's nodes should be shaped.

For example, assuming we want to use squares instead of the default circles, we can do:
```js
const svgSquare = {
shape: 'rect',
shapeProps: {
width: 20,
height: 20,
x: -10,
y: -10,
}
}

// ...

<Tree data={myTreeData} nodeSvgShape={svgSquare}>
```

### Note on overridable `shapeProps`
`shapeProps` is currently merged with `node.circle`/`leafNode.circle` (see [Styling](#styling)).

This means any properties passed in `shapeProps` will currently be overridden by **properties with the same key** in the `node.circle`/`leafNode.circle` style props.
This is to prevent breaking the legacy usage of `circleRadius` + styling via `node/leafNode` properties until it is deprecated fully in v2.

From v1.5.x onwards, it is therefore recommended to **pass all node styling properties through `shapeProps`**

## Styling
The tree's `styles` prop may be used to override any of the tree's default styling.
Expand Down
19 changes: 14 additions & 5 deletions src/Node/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ export default class Node extends React.Component {
}

render() {
const { nodeData, styles } = this.props;
const { nodeData, nodeSvgShape, styles } = this.props;
const nodeStyle = nodeData._children
? { ...styles.node }
: { ...styles.leafNode };
Expand All @@ -83,6 +83,16 @@ export default class Node extends React.Component {
transform={this.state.transform}
onClick={this.handleClick}
>
{/* TODO: DEPRECATE <circle /> */}
{this.props.circleRadius ? (
<circle r={this.props.circleRadius} style={nodeStyle.circle} />
) : (
React.createElement(nodeSvgShape.shape, {
...nodeSvgShape.shapeProps,
...nodeStyle.circle,
})
)}

<text
className="nodeNameBase"
textAnchor={this.props.textAnchor}
Expand All @@ -93,9 +103,6 @@ export default class Node extends React.Component {
>
{this.props.name}
</text>

<circle r={this.props.circleRadius} style={nodeStyle.circle} />

<text
className="nodeAttributesBase"
y="0"
Expand All @@ -117,6 +124,7 @@ export default class Node extends React.Component {
Node.defaultProps = {
textAnchor: 'start',
attributes: undefined,
circleRadius: undefined,
styles: {
node: {
circle: {},
Expand All @@ -133,12 +141,13 @@ Node.defaultProps = {

Node.propTypes = {
nodeData: PropTypes.object.isRequired,
nodeSvgShape: PropTypes.object.isRequired,
orientation: PropTypes.oneOf(['horizontal', 'vertical']).isRequired,
transitionDuration: PropTypes.number.isRequired,
onClick: PropTypes.func.isRequired,
name: PropTypes.string.isRequired,
attributes: PropTypes.object,
textAnchor: PropTypes.string,
circleRadius: PropTypes.number.isRequired,
circleRadius: PropTypes.number,
styles: PropTypes.object,
};
36 changes: 30 additions & 6 deletions src/Node/tests/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,16 @@ describe('<Node />', () => {

const mockProps = {
nodeData,
nodeSvgShape: {
shape: 'circle',
shapeProps: {
r: 10,
},
},
name: nodeData.name,
orientation: 'horizontal',
transitionDuration: 500,
onClick: () => {},
circleRadius: 10,
styles: {},
};

Expand Down Expand Up @@ -65,11 +70,11 @@ describe('<Node />', () => {
/>,
);

expect(leafNodeComponent.find('circle').prop('style')).toBe(
fixture.leafNode.circle,
expect(leafNodeComponent.find('circle').prop('fill')).toBe(
fixture.leafNode.circle.fill,
);
expect(nodeComponent.find('circle').prop('style')).toBe(
fixture.node.circle,
expect(nodeComponent.find('circle').prop('fill')).toBe(
fixture.node.circle.fill,
);
});

Expand Down Expand Up @@ -213,7 +218,26 @@ describe('<Node />', () => {
);
});

// TODO Find a way to meaningfully test `componentWillLeave`
it('allows passing SVG shape elements + shapeProps to be used as the node element', () => {
const fixture = { shape: 'ellipsis', shapeProps: { rx: 20, ry: 10 } };
const props = { ...mockProps, nodeSvgShape: fixture };
const renderedComponent = shallow(<Node {...props} />);

expect(renderedComponent.find(fixture.shape).length).toBe(1);
expect(renderedComponent.find(fixture.shape).props()).toEqual(
fixture.shapeProps,
);
});

// TODO: DEPRECATE in v2
it('falls back to legacy `<circle />` prop only if `circleRadius` is defined', () => {
const props = { ...mockProps, circleRadius: 99 };
const renderedComponent = shallow(<Node {...props} />);

expect(renderedComponent.find('circle').prop('r')).toBe(99);
});

// TODO: Find a way to meaningfully test `componentWillLeave`

// it('regresses to its parent coords when unmounting/leaving', () => {
// jest.spyOn(Node.prototype, 'applyTransform');
Expand Down
14 changes: 13 additions & 1 deletion src/Tree/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,7 @@ export default class Tree extends React.Component {
render() {
const { nodes, links } = this.generateTree();
const {
nodeSvgShape,
orientation,
translate,
pathFunc,
Expand Down Expand Up @@ -296,6 +297,7 @@ export default class Tree extends React.Component {
{nodes.map(nodeData => (
<Node
key={nodeData.id}
nodeSvgShape={nodeSvgShape}
orientation={orientation}
transitionDuration={transitionDuration}
textAnchor="start"
Expand All @@ -315,6 +317,12 @@ export default class Tree extends React.Component {
}

Tree.defaultProps = {
nodeSvgShape: {
shape: 'circle',
shapeProps: {
r: 10,
},
},
onClick: undefined,
orientation: 'horizontal',
translate: { x: 0, y: 0 },
Expand All @@ -327,12 +335,16 @@ Tree.defaultProps = {
scaleExtent: { min: 0.1, max: 1 },
nodeSize: { x: 140, y: 140 },
separation: { siblings: 1, nonSiblings: 2 },
circleRadius: 10,
circleRadius: undefined, // TODO: DEPRECATE
styles: {},
};

Tree.propTypes = {
data: PropTypes.array.isRequired,
nodeSvgShape: PropTypes.shape({
shape: PropTypes.string,
shapeProps: PropTypes.object,
}),
onClick: PropTypes.func,
orientation: PropTypes.oneOf(['horizontal', 'vertical']),
translate: PropTypes.shape({
Expand Down

0 comments on commit 4c8d520

Please sign in to comment.