* Update chart breakpoints to match the other parts or wc-admin

* Make chart have always the specified height so it matches the legend total label

* Remove obsolete test

* Remove exports no longer needed

* Remove @wordpress/viewport dependency from chart.js

* Update chart docs

* Make tooltipPosition PropType more strict
This commit is contained in:
Albert Juhé Lluveras 2018-11-15 09:27:55 -06:00 committed by GitHub
parent 0374119245
commit 8997d4ca26
9 changed files with 132 additions and 84 deletions

View File

@ -105,6 +105,7 @@ class D3Chart extends Component {
mode,
orderedKeys,
pointLabelFormat,
tooltipPosition,
tooltipFormat,
tooltipTitle,
type,
@ -116,7 +117,6 @@ class D3Chart extends Component {
const { width } = this.state;
const calculatedWidth = width || node.offsetWidth;
const calculatedHeight = height || node.offsetHeight;
const scale = width / node.offsetWidth;
const adjHeight = calculatedHeight - margin.top - margin.bottom;
const adjWidth = calculatedWidth - margin.left - margin.right;
const uniqueKeys = getUniqueKeys( data );
@ -140,7 +140,7 @@ class D3Chart extends Component {
orderedKeys: newOrderedKeys,
pointLabelFormat,
parseDate,
scale,
tooltipPosition,
tooltipFormat: d3TimeFormat( tooltipFormat ),
tooltipTitle,
type,
@ -155,7 +155,7 @@ class D3Chart extends Component {
xScale,
yMax,
yScale,
yTickOffset: getYTickOffset( adjHeight, scale, yMax ),
yTickOffset: getYTickOffset( adjHeight, yMax ),
yFormat,
valueType,
};
@ -241,6 +241,10 @@ D3Chart.propTypes = {
* if `tooltipTitle` is missing, passed to d3TimeFormat.
*/
tooltipFormat: PropTypes.string,
/**
* The position where to render the tooltip can be `over` the chart or `below` the chart.
*/
tooltipPosition: PropTypes.oneOf( [ 'below', 'over' ] ),
/**
* A string to use as a title for the tooltip. Takes preference over `tooltipFormat`.
*/
@ -280,6 +284,7 @@ D3Chart.defaultProps = {
layout: 'standard',
mode: 'item-comparison',
tooltipFormat: '%B %d, %Y',
tooltipPosition: 'over',
type: 'line',
width: 600,
xFormat: '%Y-%m-%d',

View File

@ -32,7 +32,6 @@ export default class D3Base extends Component {
drawChart: PropTypes.func.isRequired,
getParams: PropTypes.func.isRequired,
type: PropTypes.string,
width: PropTypes.number,
};
state = {
@ -41,7 +40,6 @@ export default class D3Base extends Component {
drawChart: null,
getParams: null,
type: null,
width: null,
};
chartRef = createRef();
@ -61,10 +59,6 @@ export default class D3Base extends Component {
state = { ...state, getParams: nextProps.getParams };
}
if ( nextProps.width !== prevState.width ) {
state = { ...state, width: nextProps.width };
}
if ( nextProps.type !== prevState.type ) {
state = { ...state, type: nextProps.type };
}
@ -86,7 +80,6 @@ export default class D3Base extends Component {
return (
( nextState.params !== null && ! isEqual( this.state.params, nextState.params ) ) ||
! isEqual( this.state.data, nextState.data ) ||
this.state.width !== nextState.width ||
this.state.type !== nextState.type
);
}
@ -129,6 +122,8 @@ export default class D3Base extends Component {
const svg = d3Select( this.chartRef.current )
.append( 'svg' )
.attr( 'viewBox', `0 0 ${ width } ${ height }` )
.attr( 'height', height )
.attr( 'width', width )
.attr( 'preserveAspectRatio', 'xMidYMid meet' );
if ( className ) {

View File

@ -12,6 +12,7 @@ import PropTypes from 'prop-types';
import { interpolateViridis as d3InterpolateViridis } from 'd3-scale-chromatic';
import { formatDefaultLocale as d3FormatDefaultLocale } from 'd3-format';
import Gridicon from 'gridicons';
import { withViewportMatch } from '@wordpress/viewport';
/**
* WooCommerce dependencies
@ -22,10 +23,8 @@ import { updateQueryString } from '@woocommerce/navigation';
* Internal dependencies
*/
import D3Chart from './charts';
import { gap, gaplarge } from 'stylesheets/abstracts/_variables.scss';
import { H, Section } from 'components/section';
import Legend from './legend';
import { WIDE_BREAKPOINT } from './utils';
d3FormatDefaultLocale( {
decimal: '.',
@ -60,16 +59,13 @@ function getOrderedKeys( props ) {
class Chart extends Component {
constructor( props ) {
super( props );
this.chartRef = createRef();
const wpBody = document.getElementById( 'wpbody' ).getBoundingClientRect().width;
const wpWrap = document.getElementById( 'wpwrap' ).getBoundingClientRect().width;
const calcGap = wpWrap > 782 ? gaplarge.match( /\d+/ )[ 0 ] : gap.match( /\d+/ )[ 0 ];
this.chartBodyRef = createRef();
this.state = {
data: props.data,
orderedKeys: getOrderedKeys( props ),
type: props.type,
visibleData: [ ...props.data ],
width: wpBody - 2 * calcGap,
width: 0,
};
this.handleTypeToggle = this.handleTypeToggle.bind( this );
this.handleLegendToggle = this.handleLegendToggle.bind( this );
@ -93,6 +89,7 @@ class Chart extends Component {
}
componentDidMount() {
this.updateDimensions();
window.addEventListener( 'resize', this.updateDimensions );
}
@ -140,7 +137,7 @@ class Chart extends Component {
updateDimensions() {
this.setState( {
width: this.chartRef.current.offsetWidth,
width: this.chartBodyRef.current.offsetWidth,
} );
}
@ -188,6 +185,20 @@ class Chart extends Component {
);
}
getChartHeight() {
const { isViewportLarge, isViewportMobile } = this.props;
if ( isViewportMobile ) {
return 180;
}
if ( isViewportLarge ) {
return 300;
}
return 220;
}
render() {
const { orderedKeys, type, visibleData, width } = this.state;
const {
@ -196,6 +207,8 @@ class Chart extends Component {
layout,
mode,
pointLabelFormat,
isViewportLarge,
isViewportWide,
title,
tooltipFormat,
tooltipTitle,
@ -205,11 +218,9 @@ class Chart extends Component {
valueType,
} = this.props;
let { yFormat } = this.props;
const legendDirection = layout === 'standard' && width >= WIDE_BREAKPOINT ? 'row' : 'column';
const chartDirection = layout === 'comparison' && width >= WIDE_BREAKPOINT ? 'row' : 'column';
let chartHeight = width > 1329 ? 300 : 220;
chartHeight = width <= 1329 && width > 783 ? 220 : chartHeight;
chartHeight = width <= 783 ? 180 : chartHeight;
const legendDirection = layout === 'standard' && isViewportWide ? 'row' : 'column';
const chartDirection = layout === 'comparison' && isViewportWide ? 'row' : 'column';
const chartHeight = this.getChartHeight();
const legend = (
<Legend
colorScheme={ d3InterpolateViridis }
@ -240,10 +251,10 @@ class Chart extends Component {
break;
}
return (
<div className="woocommerce-chart" ref={ this.chartRef }>
<div className="woocommerce-chart">
<div className="woocommerce-chart__header">
<H className="woocommerce-chart__title">{ title }</H>
{ width >= WIDE_BREAKPOINT && legendDirection === 'row' && legend }
{ isViewportWide && legendDirection === 'row' && legend }
{ this.renderIntervalSelector() }
<NavigableMenu
className="woocommerce-chart__types"
@ -280,29 +291,33 @@ class Chart extends Component {
'woocommerce-chart__body',
`woocommerce-chart__body-${ chartDirection }`
) }
ref={ this.chartBodyRef }
>
{ width >= WIDE_BREAKPOINT && legendDirection === 'column' && legend }
<D3Chart
colorScheme={ d3InterpolateViridis }
data={ visibleData }
dateParser={ dateParser }
height={ chartHeight }
margin={ margin }
mode={ mode }
orderedKeys={ orderedKeys }
pointLabelFormat={ pointLabelFormat }
tooltipFormat={ tooltipFormat }
tooltipTitle={ tooltipTitle }
type={ type }
interval={ interval }
width={ chartDirection === 'row' ? width - 320 : width }
xFormat={ xFormat }
x2Format={ x2Format }
yFormat={ yFormat }
valueType={ valueType }
/>
{ isViewportWide && legendDirection === 'column' && legend }
{ width > 0 && (
<D3Chart
colorScheme={ d3InterpolateViridis }
data={ visibleData }
dateParser={ dateParser }
height={ chartHeight }
margin={ margin }
mode={ mode }
orderedKeys={ orderedKeys }
pointLabelFormat={ pointLabelFormat }
tooltipFormat={ tooltipFormat }
tooltipPosition={ isViewportLarge ? 'over' : 'below' }
tooltipTitle={ tooltipTitle }
type={ type }
interval={ interval }
width={ chartDirection === 'row' ? width - 320 : width }
xFormat={ xFormat }
x2Format={ x2Format }
yFormat={ yFormat }
valueType={ valueType }
/>
) }
</div>
{ width < WIDE_BREAKPOINT && <div className="woocommerce-chart__footer">{ legend }</div> }
{ ! isViewportWide && <div className="woocommerce-chart__footer">{ legend }</div> }
</Section>
</div>
);
@ -400,4 +415,8 @@ Chart.defaultProps = {
interval: 'day',
};
export default Chart;
export default withViewportMatch( {
isViewportMobile: '< medium',
isViewportLarge: '>= large',
isViewportWide: '>= wide',
} )( Chart );

View File

@ -8,6 +8,7 @@
&.woocommerce-legend__direction-column {
border-right: 1px solid $core-grey-light-700;
min-width: 320px;
.woocommerce-chart__footer & {
border-right: none;
@ -24,7 +25,6 @@
.woocommerce-legend__direction-column & {
flex-direction: column;
height: 300px;
min-width: 320px;
overflow: auto;
.woocommerce-chart__footer & {
@ -36,6 +36,8 @@
}
.has-total.woocommerce-legend__direction-column & {
height: 250px;
.woocommerce-chart__footer & {
height: auto;
max-height: 220px;

View File

@ -176,6 +176,12 @@
}
}
}
.y-axis,
.axis-month {
.tick text {
font-size: 10px;
}
}
.focus-grid {
line {

View File

@ -172,12 +172,9 @@ describe( 'getYScale', () => {
describe( 'getYTickOffset', () => {
it( 'properly scale the y values for the y-axis ticks given the height and maximum y value', () => {
const testYTickOffset1 = getYTickOffset( 100, 1, testYMax );
const testYTickOffset1 = getYTickOffset( 100, testYMax );
expect( testYTickOffset1( 0 ) ).toEqual( 112 );
expect( testYTickOffset1( testYMax ) ).toEqual( 12 );
const testYTickOffset2 = getYTickOffset( 100, 2, testYMax );
expect( testYTickOffset2( 0 ) ).toEqual( 124 );
expect( testYTickOffset2( testYMax ) ).toEqual( 24 );
} );
} );

View File

@ -188,14 +188,13 @@ export const getYScale = ( height, yMax ) =>
/**
* Describes getyTickOffset
* @param {number} height - calculated height of the charting space
* @param {number} scale - ratio of the expected width to calculated width (given the viewbox)
* @param {number} yMax - from `getYMax`
* @returns {function} the D3 linear scale from 0 to the value from `getYMax`, offset by 12 pixels down
*/
export const getYTickOffset = ( height, scale, yMax ) =>
export const getYTickOffset = ( height, yMax ) =>
d3ScaleLinear()
.domain( [ 0, yMax ] )
.rangeRound( [ height + scale * 12, scale * 12 ] );
.rangeRound( [ height + 12, 12 ] );
/**
* Describes getyTickOffset
@ -416,10 +415,6 @@ export const drawAxis = ( node, params ) => {
)
.call( g => g.select( '.domain' ).remove() );
node
.selectAll( '.axis-month .tick text' )
.style( 'font-size', `${ Math.round( params.scale * 10 ) }px` );
node
.append( 'g' )
.attr( 'class', 'pipes' )
@ -455,10 +450,6 @@ export const drawAxis = ( node, params ) => {
.tickFormat( d => d3Format( params.yFormat )( d !== 0 ? d : 0 ) )
);
node
.selectAll( '.y-axis .tick text' )
.style( 'font-size', `${ Math.round( params.scale * 10 ) }px` );
node.selectAll( '.domain' ).remove();
node
.selectAll( '.axis' )
@ -543,19 +534,18 @@ const handleMouseOutLineChart = ( parentNode, params ) => {
params.tooltip.style( 'visibility', 'hidden' );
};
export const WIDE_BREAKPOINT = 960;
const calculateTooltipXPosition = (
elementCoords,
chartCoords,
tooltipSize,
tooltipMargin,
elementWidthRatio
elementWidthRatio,
tooltipPosition
) => {
const xPosition =
elementCoords.left + elementCoords.width * elementWidthRatio + tooltipMargin - chartCoords.left;
if ( chartCoords.width < WIDE_BREAKPOINT ) {
if ( tooltipPosition === 'below' ) {
return Math.max(
tooltipMargin,
Math.min(
@ -579,8 +569,14 @@ const calculateTooltipXPosition = (
return xPosition;
};
const calculateTooltipYPosition = ( elementCoords, chartCoords, tooltipSize, tooltipMargin ) => {
if ( chartCoords.width < WIDE_BREAKPOINT ) {
const calculateTooltipYPosition = (
elementCoords,
chartCoords,
tooltipSize,
tooltipMargin,
tooltipPosition
) => {
if ( tooltipPosition === 'below' ) {
return chartCoords.height;
}
@ -592,7 +588,7 @@ const calculateTooltipYPosition = ( elementCoords, chartCoords, tooltipSize, too
return yPosition;
};
const calculateTooltipPosition = ( element, chart, elementWidthRatio = 1 ) => {
const calculateTooltipPosition = ( element, chart, tooltipPosition, elementWidthRatio = 1 ) => {
const elementCoords = element.getBoundingClientRect();
const chartCoords = chart.getBoundingClientRect();
const tooltipSize = d3Select( '.tooltip' )
@ -600,7 +596,7 @@ const calculateTooltipPosition = ( element, chart, elementWidthRatio = 1 ) => {
.getBoundingClientRect();
const tooltipMargin = 24;
if ( chartCoords.width < WIDE_BREAKPOINT ) {
if ( tooltipPosition === 'below' ) {
elementWidthRatio = 0;
}
@ -610,9 +606,16 @@ const calculateTooltipPosition = ( element, chart, elementWidthRatio = 1 ) => {
chartCoords,
tooltipSize,
tooltipMargin,
elementWidthRatio
elementWidthRatio,
tooltipPosition
),
y: calculateTooltipYPosition(
elementCoords,
chartCoords,
tooltipSize,
tooltipMargin,
tooltipPosition
),
y: calculateTooltipYPosition( elementCoords, chartCoords, tooltipSize, tooltipMargin ),
};
};
@ -669,7 +672,11 @@ export const drawLines = ( node, data, params ) => {
return `${ label } ${ formatCurrency( d.value ) }`;
} )
.on( 'focus', ( d, i, nodes ) => {
const position = calculateTooltipPosition( d3Event.target, node.node() );
const position = calculateTooltipPosition(
d3Event.target,
node.node(),
params.tooltipPosition
);
handleMouseOverLineChart( d.date, nodes[ i ].parentNode, node, data, params, position );
} )
.on( 'blur', ( d, i, nodes ) => handleMouseOutLineChart( nodes[ i ].parentNode, params ) );
@ -717,7 +724,12 @@ export const drawLines = ( node, data, params ) => {
.attr( 'opacity', 0 )
.on( 'mouseover', ( d, i, nodes ) => {
const elementWidthRatio = i === 0 || i === params.dateSpaces.length - 1 ? 0 : 0.5;
const position = calculateTooltipPosition( d3Event.target, node.node(), elementWidthRatio );
const position = calculateTooltipPosition(
d3Event.target,
node.node(),
params.tooltipPosition,
elementWidthRatio
);
handleMouseOverLineChart( d.date, nodes[ i ].parentNode, node, data, params, position );
} )
.on( 'mouseout', ( d, i, nodes ) => handleMouseOutLineChart( nodes[ i ].parentNode, params ) );
@ -782,7 +794,7 @@ export const drawBars = ( node, data, params ) => {
} )
.on( 'focus', ( d, i, nodes ) => {
const targetNode = d.value > 0 ? d3Event.target : d3Event.target.parentNode;
const position = calculateTooltipPosition( targetNode, node.node() );
const position = calculateTooltipPosition( targetNode, node.node(), params.tooltipPosition );
handleMouseOverBarChart( d.date, nodes[ i ].parentNode, node, data, params, position );
} )
.on( 'blur', ( d, i, nodes ) => handleMouseOutBarChart( nodes[ i ].parentNode, params ) );
@ -796,7 +808,11 @@ export const drawBars = ( node, data, params ) => {
.attr( 'height', params.height )
.attr( 'opacity', '0' )
.on( 'mouseover', ( d, i, nodes ) => {
const position = calculateTooltipPosition( d3Event.target, node.node() );
const position = calculateTooltipPosition(
d3Event.target,
node.node(),
params.tooltipPosition
);
handleMouseOverBarChart( d.date, nodes[ i ].parentNode, node, data, params, position );
} )
.on( 'mouseout', ( d, i, nodes ) => handleMouseOutBarChart( nodes[ i ].parentNode, params ) );

View File

@ -23,10 +23,3 @@ $light-gray-500: $core-grey-light-500;
$dark-gray-300: $core-grey-dark-300;
$dark-gray-900: $core-grey-dark-900;
$alert-red: $error-red;
/* stylelint-disable */
:export {
gaplarge: $gap-large;
gap: $gap;
}
/* stylelint-enable */

View File

@ -239,6 +239,13 @@ The list of labels for this chart.
A datetime formatting string to format the date displayed as the title of the toolip
if `tooltipTitle` is missing, passed to d3TimeFormat.
### `tooltipPosition`
- Type: String
- Default: `'over'`
The position where to render the tooltip can be `over` the chart or `below` the chart.
### `tooltipTitle`
- Type: String
@ -332,6 +339,14 @@ Handles `onMouseEnter`/`onMouseLeave` events.
Display legend items as a `row` or `column` inside a flex-box.
### `itemsLabel`
- Type: String
- Default: null
Label to describe the legend items. It will be displayed in the legend of
comparison charts when there are many.
### `valueType`
- Type: String