Merge pull request woocommerce/woocommerce-admin#73 from woocommerce/add/d3-base-component
D3: adding base component
This commit is contained in:
commit
a932758fc7
|
@ -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
|
|
@ -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;
|
|
@ -0,0 +1,3 @@
|
|||
.d3-base {
|
||||
width:100%;
|
||||
}
|
|
@ -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 );
|
||||
} );
|
||||
} );
|
|
@ -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"
|
||||
|
|
Loading…
Reference in New Issue