Merge pull request woocommerce/woocommerce-admin#360 from woocommerce/add/chart-intervals

Chart Component: Add/chart intervals
This commit is contained in:
Robert Elliott 2018-09-06 18:10:37 +02:00 committed by GitHub
commit 2df9b05b5f
9 changed files with 135 additions and 99 deletions

View File

@ -6,13 +6,12 @@ A simple D3 line and bar chart component for timeseries data in React.
## Usage
```jsx
<D3Chart
className="woocommerce-dashboard__*"
<Chart
data={ timeseries }
height={ 200 }
margin={ { bottom: 30, left: 40, right: 0, top: 20 } }
tooltipFormat={ 'Date is %Y-%m-%d' }
type={ 'bar' }
width={ 600 }
xFormat={ '%d' }
yFormat={ '.3s' }
/>
```
@ -21,7 +20,7 @@ This component accepts timeseries `data` prop in the following format (with date
```
[
{
date: 'YYYY-mm-dd', // string
date: '%Y-%m-%dT%H:%M:%S', // string
category1: value, // number
category2: value, // number
...
@ -33,7 +32,7 @@ For example:
```
[
{
date: '2018-06-25',
date: '2018-06-25T00:00:00',
category1: 1234.56,
category2: 9876,
...
@ -47,68 +46,9 @@ Required props are marked with `*`.
Name | Type | Default | Description
--- | --- | --- | ---
`data`* | `array` | none | An array of data as specified above
`height` | `number` | `200` | Relative viewpoirt height of the `svg`
`margin` | `object` | `{ bottom: 30, left: 40, right: 0, top: 20 }` | Margins for axis and chart padding
`orderedKeys` | `array` | `getOrderedKeys` | Override for `getOrderedKeys` and used by the `<Legend />` (below)
`data`* | `array` | none | An array of data as specified above(below)
`type`* | `string` | `line` | Chart type of either `line` or `bar`
`width` | `number` | `600` | Relative viewport width of the `svg`
D3 Chart Legend
===
A legend specifically designed for the WooCommerce admin charts.
## Usage
```jsx
<Legend
className={ 'woocommerce-legend' }
data={ data }
handleLegendHover={ this.handleLegendHover }
handleLegendToggle={ this.handleLegendToggle }
legendDirection={ legendDirection }
/>
```
### Expected Data Format
This component needs to include the category keys present in the D3 Chart Component's `orderedKeys`, in fact, you should use the same `orderedKeys` for both the legend `data` and the chart.
The `handleLegendHover` could toggle the `focus` parameter to highlight the category that has the mouse over it.
The `handleLegendToggle` could toggle the `visible` parameter to hide/show the category that has been selected.
```
[
{
key: "Product", // string
total: number, // number
visible: true, // boolean
focus: true, // boolean
},
...
]
```
For example:
```
[
{
date: 'Hoodie',
total: 1234.56,
visible: true,
focus: true,
...
},
...
]
```
### Props
Required props are marked with `*`.
Name | Type | Default | Description
--- | --- | --- | ---
`data`* | `array` | none | An array of `orderedKeys` as specified above
`handleLegendHover` | `function` | none | Handles `onMouseEnter`/`onMouseLeave` events
`handleLegendToggle` | `function` | none | Handles `onClick` event
`legendDirection` | `string` | none | Display legend items as a `row` or `column` inside a flex-box
`title` | `string` | none | Chart title
`tooltipFormat` | `string` | `%Y-%m-%d` | Title and format of the tooltip title
`xFormat` | `string` | `%Y-%m-%d` | d3TimeFormat of the x-axis values
`yFormat` | `string` | `.3s` | d3Format of the y-axis values

View File

@ -7,7 +7,6 @@ import { isEqual } from 'lodash';
import { Component, createRef } from '@wordpress/element';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import { format as d3Format } from 'd3-format';
import { timeFormat as d3TimeFormat } from 'd3-time-format';
import { select as d3Select } from 'd3-selection';
@ -89,7 +88,17 @@ class D3Chart extends Component {
}
getParams( node ) {
const { colorScheme, data, height, margin, orderedKeys, type, xFormat, yFormat } = this.props;
const {
colorScheme,
data,
height,
margin,
orderedKeys,
tooltipFormat,
type,
xFormat,
yFormat,
} = this.props;
const { width } = this.state;
const calculatedWidth = width || node.offsetWidth;
const calculatedHeight = height || node.offsetHeight;
@ -113,6 +122,7 @@ class D3Chart extends Component {
margin,
orderedKeys: newOrderedKeys,
scale,
tooltipFormat: d3TimeFormat( tooltipFormat ),
type,
uniqueDates,
uniqueKeys,
@ -124,7 +134,7 @@ class D3Chart extends Component {
yMax,
yScale,
yTickOffset: getYTickOffset( adjHeight, scale, yMax ),
yFormat: d3Format( yFormat ),
yFormat,
};
}
@ -168,9 +178,9 @@ D3Chart.propTypes = {
*/
height: PropTypes.number,
/**
* @todo Remove not used?
* Interval specification (hourly, daily, weekly etc.)
*/
legend: PropTypes.array,
interval: PropTypes.oneOf( [ 'hour', 'day', 'week', 'month', 'quater', 'year' ] ),
/**
* Margins for axis and chart padding.
*/
@ -184,6 +194,10 @@ D3Chart.propTypes = {
* The list of labels for this chart.
*/
orderedKeys: PropTypes.array,
/**
* A datetime formatting string to format the title of the toolip, passed to d3TimeFormat.
*/
tooltipFormat: PropTypes.string,
/**
* Chart type of either `line` or `bar`.
*/
@ -211,10 +225,11 @@ D3Chart.defaultProps = {
right: 0,
top: 20,
},
tooltipFormat: '%Y-%m-%d',
type: 'line',
width: 600,
xFormat: '%Y-%m-%d',
yFormat: ',.0f',
yFormat: '.3s',
};
export default D3Chart;

View File

@ -0,0 +1,30 @@
/** @format */
/**
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import { Component } from '@wordpress/element';
/**
* Internal dependencies
*/
import Card from 'components/card';
import Chart from './index';
import dummyOrders from './test/fixtures/dummy-hour';
class WidgetCharts extends Component {
render() {
return (
<Card title={ __( 'Test Categories', 'wc-admin' ) }>
<Chart
data={ dummyOrders }
tooltipFormat={ 'Hour of %H' }
type={ 'bar' }
xFormat={ '%H' }
/>
</Card>
);
}
}
export default WidgetCharts;

View File

@ -158,8 +158,11 @@ class Chart extends Component {
height={ 300 }
margin={ margin }
orderedKeys={ orderedKeys }
type={ 'line' }
tooltipFormat={ this.props.tooltipFormat }
type={ this.props.type }
width={ chartDirection === 'row' ? width - 320 : width }
xFormat={ this.props.xFormat }
yFormat={ this.props.yFormat }
/>
</div>
{ width < WIDE_BREAKPOINT && <div className="woocommerce-chart__footer">{ legend }</div> }
@ -177,10 +180,29 @@ Chart.propTypes = {
* A title describing this chart.
*/
title: PropTypes.string,
/**
* A datetime formatting string to format the title of the toolip, passed to d3TimeFormat.
*/
tooltipFormat: PropTypes.string,
/**
* Chart type of either `line` or `bar`.
*/
type: PropTypes.oneOf( [ 'bar', 'line' ] ),
/**
* A datetime formatting string, passed to d3TimeFormat.
*/
xFormat: PropTypes.string,
/**
* A number formatting string, passed to d3Format.
*/
yFormat: PropTypes.string,
};
Chart.defaultProps = {
data: [],
tooltipFormat: '%Y-%m-%d',
xFormat: '%Y-%m-%d',
yFormat: '.3s',
};
export default Chart;

View File

@ -0,0 +1,25 @@
/** @format */
// /**
// * /* eslint-disable quote-props
// *
// * @format
// */
export default [
{
date: '2018-08-01T00:00:00',
'Custom (Aug 1, 2018)': 58929.99,
'Previous Period (Jul 31, 2018)': 160130.74000000002,
},
{
date: '2018-08-01T01:00:00',
'Custom (Aug 1, 2018)': 3805.56,
'Previous Period (Jul 31, 2018)': 0,
},
{
date: '2018-08-01T02:00:00',
'Custom (Aug 1, 2018)': 3805.56,
'Previous Period (Jul 31, 2018)': 0,
},
];

View File

@ -1,3 +1,5 @@
/** @format */
/**
* /* eslint-disable quote-props
*
@ -6,7 +8,7 @@
export default [
{
date: '2018-05-30',
date: '2018-05-30T00:00:00',
Polo: 2704659,
'T-Shirt': 4499890,
Hoodie: 2159981,
@ -14,7 +16,7 @@ export default [
Cap: 10604510,
},
{
date: '2018-05-31',
date: '2018-05-31T00:00:00',
Polo: 2027307,
'T-Shirt': 3277946,
Hoodie: 1420518,
@ -22,7 +24,7 @@ export default [
Cap: 7017731,
},
{
date: '2018-06-01',
date: '2018-06-01T00:00:00',
Polo: 1208495,
'T-Shirt': 2141490,
Hoodie: 1058031,
@ -30,7 +32,7 @@ export default [
Cap: 5355235,
},
{
date: '2018-06-02',
date: '2018-06-02T00:00:00',
Polo: 1140516,
'T-Shirt': 1938695,
Hoodie: 925060,
@ -38,7 +40,7 @@ export default [
Cap: 4782119,
},
{
date: '2018-06-03',
date: '2018-06-03T00:00:00',
Polo: 894368,
'T-Shirt': 1558919,
Hoodie: 725973,
@ -46,7 +48,7 @@ export default [
Cap: 3596343,
},
{
date: '2018-06-04',
date: '2018-06-04T00:00:00',
Polo: 737462,
'T-Shirt': 1345341,
Hoodie: 679201,

View File

@ -57,12 +57,12 @@ const orderedKeys = [
},
];
const orderedDates = [
'2018-05-30',
'2018-05-31',
'2018-06-01',
'2018-06-02',
'2018-06-03',
'2018-06-04',
'2018-05-30T00:00:00',
'2018-05-31T00:00:00',
'2018-06-01T00:00:00',
'2018-06-02T00:00:00',
'2018-06-03T00:00:00',
'2018-06-04T00:00:00',
];
const testUniqueKeys = getUniqueKeys( dummyOrders );
const testOrderedKeys = getOrderedKeys( dummyOrders, testUniqueKeys );
@ -75,7 +75,7 @@ const testYScale = getYScale( 100, testYMax );
describe( 'parseDate', () => {
it( 'correctly parse date in the expected format', () => {
const testDate = parseDate( '2018-06-30' );
const testDate = parseDate( '2018-06-30T00:00:00' );
const expectedDate = new Date( Date.UTC( 2018, 5, 30 ) );
expect( testDate.getTime() ).toEqual( expectedDate.getTime() );
} );
@ -182,13 +182,13 @@ describe( 'getYTickOffset', () => {
describe( 'getdateSpaces', () => {
it( 'return an array used to space out the mouseover rectangles, used for tooltips', () => {
const testDateSpaces = getDateSpaces( testUniqueDates, 100, testXLineScale );
expect( testDateSpaces[ 0 ].date ).toEqual( '2018-05-30' );
expect( testDateSpaces[ 0 ].date ).toEqual( '2018-05-30T00:00:00' );
expect( testDateSpaces[ 0 ].start ).toEqual( 0 );
expect( testDateSpaces[ 0 ].width ).toEqual( 10 );
expect( testDateSpaces[ 3 ].date ).toEqual( '2018-06-02' );
expect( testDateSpaces[ 3 ].date ).toEqual( '2018-06-02T00:00:00' );
expect( testDateSpaces[ 3 ].start ).toEqual( 50 );
expect( testDateSpaces[ 3 ].width ).toEqual( 20 );
expect( testDateSpaces[ testDateSpaces.length - 1 ].date ).toEqual( '2018-06-04' );
expect( testDateSpaces[ testDateSpaces.length - 1 ].date ).toEqual( '2018-06-04T00:00:00' );
expect( testDateSpaces[ testDateSpaces.length - 1 ].start ).toEqual( 90 );
expect( testDateSpaces[ testDateSpaces.length - 1 ].width ).toEqual( 10 );
} );

View File

@ -15,9 +15,9 @@ import {
} from 'd3-scale';
import { mouse as d3Mouse, select as d3Select } from 'd3-selection';
import { line as d3Line } from 'd3-shape';
import { timeFormat as d3TimeFormat, utcParse as d3UTCParse } from 'd3-time-format';
import { utcParse as d3UTCParse } from 'd3-time-format';
export const parseDate = d3UTCParse( '%Y-%m-%d' );
export const parseDate = d3UTCParse( '%Y-%m-%dT%H:%M:%S' );
/**
* Describes `getUniqueKeys`
@ -214,7 +214,7 @@ export const drawAxis = ( node, params ) => {
.call(
d3AxisBottom( xScale )
.tickValues( params.uniqueDates.map( d => ( params.type === 'line' ? new Date( d ) : d ) ) )
.tickFormat( d => d3TimeFormat( '%d' )( d instanceof Date ? d : new Date( d ) ) )
.tickFormat( d => params.xFormat( d instanceof Date ? d : new Date( d ) ) )
);
node
@ -235,7 +235,7 @@ export const drawAxis = ( node, params ) => {
.call(
d3AxisLeft( params.yTickOffset )
.tickValues( yGrids )
.tickFormat( d => ( d !== 0 ? d3Format( '.3s' )( d ) : 0 ) )
.tickFormat( d => ( d !== 0 ? d3Format( params.yFormat )( d ) : 0 ) )
);
node
@ -270,7 +270,7 @@ const showTooltip = ( node, params, d ) => {
.style( 'top', yPosition + 'px' )
.style( 'display', 'inline-block' ).html( `
<div>
<h4>${ d.date }</h4>
<h4>${ params.tooltipFormat( d.date instanceof Date ? d.date : new Date( d.date ) ) }</h4>
<ul>
${ keys.join( '' ) }
</ul>

View File

@ -9,6 +9,7 @@ import { Component, Fragment } from '@wordpress/element';
* Internal dependencies
*/
import './style.scss';
import ChartExample from 'components/chart/example-hour';
import Header from 'layout/header';
import StorePerformance from './store-performance';
import TopSellingProducts from './top-selling-products';
@ -19,6 +20,7 @@ export default class Dashboard extends Component {
<Fragment>
<Header sections={ [ __( 'Dashboard', 'wc-admin' ) ] } />
<StorePerformance />
<ChartExample />
<div className="woocommerce-dashboard__columns">
<div>
<TopSellingProducts />