Merge pull request woocommerce/woocommerce-admin#381 from woocommerce/add/chart-legend-order

Chart Component: remove chart legend ordering for layout=standard and color scales
This commit is contained in:
Robert Elliott 2018-09-10 16:04:36 +02:00 committed by GitHub
commit e38284eb96
7 changed files with 75 additions and 39 deletions

View File

@ -305,7 +305,7 @@ export class RevenueReport extends Component {
return (
<Card title="">
<Chart data={ chartData } title={ selectedChart.label } />
<Chart data={ chartData } title={ selectedChart.label } dateParser={ '%Y-%m-%d' } />
</Card>
);
}

View File

@ -8,6 +8,7 @@ A simple D3 line and bar chart component for timeseries data in React.
```jsx
<Chart
data={ timeseries }
dateParser={ '%Y-%m-%d' }
tooltipFormat={ 'Date is %Y-%m-%d' }
type={ 'bar' }
xFormat={ '%d' }
@ -20,7 +21,7 @@ This component accepts timeseries `data` prop in the following format (with date
```
[
{
date: '%Y-%m-%dT%H:%M:%S', // string
date: '%Y-%m-%d', // string in `dateParser` format (see below)
category1: value, // number
category2: value, // number
...
@ -32,7 +33,7 @@ For example:
```
[
{
date: '2018-06-25T00:00:00',
date: '2018-06-25',
category1: 1234.56,
category2: 9876,
...
@ -47,6 +48,7 @@ Required props are marked with `*`.
Name | Type | Default | Description
--- | --- | --- | ---
`data`* | `array` | none | An array of data as specified above(below)
`dateParser` | `string` | `%Y-%m-%dT%H:%M:%S` | Format to parse datetimes in the data
`type`* | `string` | `line` | Chart type of either `line` or `bar`
`title` | `string` | none | Chart title
`tooltipFormat` | `string` | `%Y-%m-%d` | Title and format of the tooltip title

View File

@ -7,7 +7,7 @@ import { isEqual } from 'lodash';
import { Component, createRef } from '@wordpress/element';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import { timeFormat as d3TimeFormat } from 'd3-time-format';
import { timeFormat as d3TimeFormat, utcParse as d3UTCParse } from 'd3-time-format';
import { select as d3Select } from 'd3-selection';
/**
@ -91,6 +91,7 @@ class D3Chart extends Component {
const {
colorScheme,
data,
dateParser,
height,
margin,
orderedKeys,
@ -111,7 +112,8 @@ class D3Chart extends Component {
const lineData = getLineData( data, newOrderedKeys );
const yMax = getYMax( lineData );
const yScale = getYScale( adjHeight, yMax );
const uniqueDates = getUniqueDates( lineData );
const parseDate = d3UTCParse( dateParser );
const uniqueDates = getUniqueDates( lineData, parseDate );
const xLineScale = getXLineScale( uniqueDates, adjWidth );
const xScale = getXScale( uniqueDates, adjWidth );
return {
@ -122,6 +124,7 @@ class D3Chart extends Component {
lineData,
margin,
orderedKeys: newOrderedKeys,
parseDate,
scale,
tooltipFormat: d3TimeFormat( tooltipFormat ),
type,
@ -175,6 +178,10 @@ D3Chart.propTypes = {
* An array of data.
*/
data: PropTypes.array.isRequired,
/**
* Format to parse dates into d3 time format
*/
dateParser: PropTypes.string.isRequired,
/**
* Relative viewpoirt height of the `svg`.
*/
@ -224,6 +231,7 @@ D3Chart.propTypes = {
D3Chart.defaultProps = {
data: [],
dateParser: '%Y-%m-%dT%H:%M:%S',
height: 200,
margin: {
bottom: 30,

View File

@ -93,7 +93,7 @@ class WidgetCharts extends Component {
</ul>
</Card>
<Card title={ __( 'Store Charts', 'wc-admin' ) }>
<Chart data={ this.state.someOrders } title="Example Chart" />
<Chart data={ this.state.someOrders } title="Example Chart" layout="comparison" />
</Card>
</Fragment>
);

View File

@ -17,22 +17,24 @@ import { gap, gaplarge } from 'stylesheets/abstracts/_variables.scss';
const WIDE_BREAKPOINT = 1100;
function getOrderedKeys( data ) {
return [
function getOrderedKeys( props ) {
const updatedKeys = [
...new Set(
data.reduce( ( accum, curr ) => {
props.data.reduce( ( accum, curr ) => {
Object.keys( curr ).forEach( key => key !== 'date' && accum.push( key ) );
return accum;
}, [] )
),
]
.map( key => ( {
key,
total: data.reduce( ( a, c ) => a + c[ key ], 0 ),
visible: true,
focus: true,
} ) )
.sort( ( a, b ) => b.total - a.total );
].map( key => ( {
key,
total: props.data.reduce( ( a, c ) => a + c[ key ], 0 ),
visible: true,
focus: true,
} ) );
if ( props.layout === 'comparison' ) {
updatedKeys.sort( ( a, b ) => b.total - a.total );
}
return updatedKeys;
}
/**
@ -47,7 +49,7 @@ class Chart extends Component {
const calcGap = wpWrap > 782 ? gaplarge.match( /\d+/ )[ 0 ] : gap.match( /\d+/ )[ 0 ];
this.state = {
data: props.data,
orderedKeys: getOrderedKeys( props.data ),
orderedKeys: getOrderedKeys( props ),
visibleData: [ ...props.data ],
width: wpBody - 2 * calcGap,
};
@ -59,7 +61,7 @@ class Chart extends Component {
componentDidUpdate( prevProps ) {
const { data } = this.props;
const orderedKeys = getOrderedKeys( data );
const orderedKeys = getOrderedKeys( this.props );
if ( ! isEqual( [ ...data ].sort(), [ ...prevProps.data ].sort() ) ) {
/* eslint-disable react/no-did-update-set-state */
this.setState( {
@ -129,10 +131,18 @@ class Chart extends Component {
render() {
const { orderedKeys, visibleData, width } = this.state;
const legendDirection =
this.props.layout === 'standard' && width > WIDE_BREAKPOINT ? 'row' : 'column';
const chartDirection =
this.props.layout === 'comparison' && width > WIDE_BREAKPOINT ? 'row' : 'column';
const {
dateParser,
layout,
title,
tooltipFormat,
type,
xFormat,
x2Format,
yFormat,
} = this.props;
const legendDirection = layout === 'standard' && width > WIDE_BREAKPOINT ? 'row' : 'column';
const chartDirection = layout === 'comparison' && width > WIDE_BREAKPOINT ? 'row' : 'column';
const legend = (
<Legend
className={ 'woocommerce-chart__legend' }
@ -152,7 +162,7 @@ class Chart extends Component {
return (
<div className="woocommerce-chart" ref={ this.chartRef }>
<div className="woocommerce-chart__header">
<span className="woocommerce-chart__title">{ this.props.title }</span>
<span className="woocommerce-chart__title">{ title }</span>
{ width > WIDE_BREAKPOINT && legendDirection === 'row' && legend }
</div>
<div
@ -165,15 +175,16 @@ class Chart extends Component {
<D3Chart
colorScheme={ d3InterpolateViridis }
data={ visibleData }
dateParser={ dateParser }
height={ 300 }
margin={ margin }
orderedKeys={ orderedKeys }
tooltipFormat={ this.props.tooltipFormat }
type={ this.props.type }
tooltipFormat={ tooltipFormat }
type={ type }
width={ chartDirection === 'row' ? width - 320 : width }
xFormat={ this.props.xFormat }
x2Format={ this.props.x2Format }
yFormat={ this.props.yFormat }
xFormat={ xFormat }
x2Format={ x2Format }
yFormat={ yFormat }
/>
</div>
{ width < WIDE_BREAKPOINT && <div className="woocommerce-chart__footer">{ legend }</div> }
@ -187,6 +198,10 @@ Chart.propTypes = {
* An array of data.
*/
data: PropTypes.array.isRequired,
/**
* Format to parse dates into d3 time format
*/
dateParser: PropTypes.string.isRequired,
/**
* A datetime formatting string to format the title of the toolip, passed to d3TimeFormat.
*/
@ -219,6 +234,7 @@ Chart.propTypes = {
Chart.defaultProps = {
data: [],
dateParser: '%Y-%m-%dT%H:%M:%S',
tooltipFormat: '%Y-%m-%d',
xFormat: '%d',
x2Format: '%b %Y',

View File

@ -4,6 +4,7 @@
* @format
*/
// import { noop } from 'lodash';
import { utcParse as d3UTCParse } from 'd3-time-format';
/**
* Internal dependencies
@ -21,7 +22,6 @@ import {
getYMax,
getYScale,
getYTickOffset,
parseDate,
} from '../utils';
const orderedKeys = [
@ -64,10 +64,11 @@ const orderedDates = [
'2018-06-03T00:00:00',
'2018-06-04T00:00:00',
];
const parseDate = d3UTCParse( '%Y-%m-%dT%H:%M:%S' );
const testUniqueKeys = getUniqueKeys( dummyOrders );
const testOrderedKeys = getOrderedKeys( dummyOrders, testUniqueKeys );
const testLineData = getLineData( dummyOrders, testOrderedKeys );
const testUniqueDates = getUniqueDates( testLineData );
const testUniqueDates = getUniqueDates( testLineData, parseDate );
const testXScale = getXScale( testUniqueDates, 100 );
const testXLineScale = getXLineScale( testUniqueDates, 100 );
const testYMax = getYMax( testLineData );

View File

@ -15,14 +15,11 @@ import {
} from 'd3-scale';
import { mouse as d3Mouse, select as d3Select } from 'd3-selection';
import { line as d3Line } from 'd3-shape';
import { utcParse as d3UTCParse } from 'd3-time-format';
/**
* Internal dependencies
*/
import { formatCurrency } from 'lib/currency';
export const parseDate = d3UTCParse( '%Y-%m-%dT%H:%M:%S' );
function decodeSymbol( str ) {
return str.replace( /&#(\d+);/g, ( match, dec ) => String.fromCharCode( dec ) );
}
@ -87,9 +84,10 @@ export const getLineData = ( data, orderedKeys ) =>
/**
* Describes `getUniqueDates`
* @param {array} lineData - from `GetLineData`
* @param {function} parseDate - D3 time format parser
* @returns {array} an array of unique date values sorted from earliest to latest
*/
export const getUniqueDates = lineData => {
export const getUniqueDates = ( lineData, parseDate ) => {
return [
...new Set(
lineData.reduce( ( accum, { values } ) => {
@ -101,10 +99,21 @@ export const getUniqueDates = lineData => {
};
export const getColor = ( key, params ) => {
const keyValue =
params.orderedKeys.length > 1
? findIndex( params.orderedKeys, d => d.key === key ) / ( params.orderedKeys.length - 1 )
: 0;
const smallColorScales = [
[],
[ 0.5 ],
[ 0.333, 0.667 ],
[ 0.2, 0.5, 0.8 ],
[ 0.12, 0.375, 0.625, 0.88 ],
];
let keyValue = 0;
const len = params.orderedKeys.length;
const idx = findIndex( params.orderedKeys, d => d.key === key );
if ( len < 5 ) {
keyValue = smallColorScales[ len ][ idx ];
} else {
keyValue = idx / ( params.orderedKeys.length - 1 );
}
return params.colorScheme( keyValue );
};