Merge pull request woocommerce/woocommerce-admin#73 from woocommerce/add/d3-base-component

D3: adding base component
This commit is contained in:
Robert Elliott 2018-06-03 02:15:30 +02:00 committed by GitHub
commit a932758fc7
5 changed files with 152 additions and 0 deletions

View File

@ -0,0 +1,23 @@
# D3 Base Component
Integrate React Lifecyle methods with d3.js charts.
### Base Component Responsibilities
* Create and manage mounting and unmounting parent `div` and `svg`
* Handle resize events, resulting re-renders, and event listeners
* Handle re-renders as a result of new props
## Props
### className
{ string } A class to be applied to the parent `div`
### getParams( node )
{ function } A function returning an object containing required properties for drawing a chart. This object is created before re-render, making it an ideal place for calculating scales and other props or user input based properties.
* `svg` { node } The parent `div`. Useful for calculating available widths
### drawChart( svg, params )
{ function } draw the chart
* `svg` { node } Base element
* `params` { Object } Properties created by the `getParams` function

View File

@ -0,0 +1,80 @@
/** @format */
/**
* External dependencies
*/
import { Component } from '@wordpress/element';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import { select as d3Select } from 'd3-selection';
class D3Base extends Component {
constructor() {
super( ...arguments );
this.updateParams = this.updateParams.bind( this );
this.setNodeRef = this.setNodeRef.bind( this );
this.state = {};
}
componentDidMount() {
window.addEventListener( 'resize', this.updateParams );
this.updateParams();
}
componentWillReceiveProps( nextProps ) {
this.updateParams( nextProps );
}
componentDidUpdate() {
this.draw();
}
componentWillUnmount() {
window.removeEventListener( 'resize', this.updateParams );
delete this.node;
}
updateParams( nextProps ) {
const getParams = ( nextProps && nextProps.getParams ) || this.props.getParams;
this.setState( getParams( this.node ), this.draw );
}
draw() {
this.props.drawChart( this.createNewContext(), this.state );
}
createNewContext() {
const { className } = this.props;
const { width, height } = this.state;
d3Select( this.node )
.selectAll( 'svg' )
.remove();
const newNode = d3Select( this.node )
.append( 'svg' )
.attr( 'class', `${ className }__viewbox` )
.attr( 'viewBox', `0 0 ${ width } ${ height }` )
.attr( 'preserveAspectRatio', 'xMidYMid meet' )
.append( 'g' );
return newNode;
}
setNodeRef( node ) {
this.node = node;
}
render() {
return (
<div className={ classNames( 'd3-base', this.props.className ) } ref={ this.setNodeRef } />
);
}
}
D3Base.propTypes = {
className: PropTypes.string,
drawChart: PropTypes.func.isRequired,
getParams: PropTypes.func.isRequired,
};
export default D3Base;

View File

@ -0,0 +1,3 @@
.d3-base {
width:100%;
}

View File

@ -0,0 +1,45 @@
/**
* External dependencies
*
* @format
*/
import { shallow, mount } from 'enzyme';
import { noop } from 'lodash';
/**
* Internal dependencies
*/
import D3Base from '../index';
describe( 'D3base', () => {
const shallowWithoutLifecycle = arg => shallow( arg, { disableLifecycleMethods: true } );
test( 'should have d3Base class', () => {
const base = shallowWithoutLifecycle( <D3Base drawChart={ noop } getParams={ noop } /> );
expect( base.find( '.d3-base' ) ).toHaveLength( 1 );
} );
test( 'should render an svg', () => {
const base = mount( <D3Base drawChart={ noop } getParams={ noop } /> );
expect( base.render().find( 'svg' ) ).toHaveLength( 1 );
} );
test( 'should render a result of the drawChart prop', () => {
const drawChart = svg => {
return svg.append( 'circle' );
};
const base = mount( <D3Base drawChart={ drawChart } getParams={ noop } /> );
expect( base.render().find( 'circle' ) ).toHaveLength( 1 );
} );
test( 'should pass a property of getParams output to drawChart function', () => {
const getParams = () => ( {
tagName: 'circle',
} );
const drawChart = ( svg, params ) => {
return svg.append( params.tagName );
};
const base = mount( <D3Base drawChart={ drawChart } getParams={ getParams } /> );
expect( base.render().find( 'circle' ) ).toHaveLength( 1 );
} );
} );

View File

@ -63,6 +63,7 @@
},
"dependencies": {
"classnames": "^2.2.5",
"d3-selection": "^1.3.0",
"lodash": "^4.17.10",
"prop-types": "^15.6.1",
"react-slot-fill": "^2.0.1"