Hide day from X axis when the time period is too long (https://github.com/woocommerce/woocommerce-admin/pull/525)
* Refactor getXTicks * Hide day from X axis when the time period is too long * Fix X axis labels misaligned in charts * Align axis ticks to the first day of the month when not displaying the day number * Store threshold in a variable * Add missing JSDoc * Fix charts hanging when the number of days selected was exactly 180 * Improve function naming * Fix dayTicksThreshold mismatch between date format and chart layout
This commit is contained in:
parent
e8fe6bac83
commit
36c37afe49
|
@ -40,7 +40,7 @@ class ReportChart extends Component {
|
|||
|
||||
const currentInterval = getIntervalForQuery( query );
|
||||
const allowedIntervals = getAllowedIntervalsForQuery( query );
|
||||
const formats = getDateFormatsForInterval( currentInterval );
|
||||
const formats = getDateFormatsForInterval( currentInterval, primaryData.data.intervals.length );
|
||||
const { primary, secondary } = getCurrentDates( query );
|
||||
const primaryKey = `${ primary.label } (${ primary.range })`;
|
||||
const secondaryKey = `${ secondary.label } (${ secondary.range })`;
|
||||
|
|
|
@ -99,6 +99,7 @@ class D3Chart extends Component {
|
|||
dateParser,
|
||||
height,
|
||||
layout,
|
||||
interval,
|
||||
margin,
|
||||
mode,
|
||||
orderedKeys,
|
||||
|
@ -125,7 +126,7 @@ class D3Chart extends Component {
|
|||
const uniqueDates = getUniqueDates( lineData, parseDate );
|
||||
const xLineScale = getXLineScale( uniqueDates, adjWidth );
|
||||
const xScale = getXScale( uniqueDates, adjWidth );
|
||||
const xTicks = getXTicks( uniqueDates, adjWidth, layout );
|
||||
const xTicks = getXTicks( uniqueDates, adjWidth, layout, interval );
|
||||
return {
|
||||
colorScheme,
|
||||
dateSpaces: getDateSpaces( uniqueDates, adjWidth, xLineScale ),
|
||||
|
|
|
@ -20,6 +20,7 @@ import { format as formatDate } from '@wordpress/date';
|
|||
* Internal dependencies
|
||||
*/
|
||||
import { formatCurrency } from 'lib/currency';
|
||||
import { dayTicksThreshold } from 'lib/date';
|
||||
|
||||
/**
|
||||
* Describes `smallestFactor`
|
||||
|
@ -27,15 +28,16 @@ import { formatCurrency } from 'lib/currency';
|
|||
* @returns {integer} smallest factor of num
|
||||
*/
|
||||
export const getFactors = inputNum => {
|
||||
const num_factors = [];
|
||||
const numFactors = [];
|
||||
for ( let i = 1; i <= Math.floor( Math.sqrt( inputNum ) ); i += 1 ) {
|
||||
if ( inputNum % i === 0 ) {
|
||||
num_factors.push( i );
|
||||
inputNum / i !== i && num_factors.push( inputNum / i );
|
||||
numFactors.push( i );
|
||||
inputNum / i !== i && numFactors.push( inputNum / i );
|
||||
}
|
||||
}
|
||||
num_factors.sort( ( x, y ) => x - y ); // numeric sort
|
||||
return num_factors;
|
||||
numFactors.sort( ( x, y ) => x - y ); // numeric sort
|
||||
|
||||
return numFactors;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -207,71 +209,113 @@ export const getLine = ( xLineScale, yScale ) =>
|
|||
.y( d => yScale( d.value ) );
|
||||
|
||||
/**
|
||||
* Describes getXTicks
|
||||
* @param {array} uniqueDates - all the unique dates from the input data for the chart
|
||||
* Calculate the maximum number of ticks allowed in the x-axis based on the width and layout of the chart
|
||||
* @param {integer} width - calculated page width
|
||||
* @param {string} layout - standard, comparison or compact chart types
|
||||
* @returns {integer} number of x-axis ticks based on width and chart layout
|
||||
*/
|
||||
export const getXTicks = ( uniqueDates, width, layout ) => {
|
||||
// caluclate the maximum number of ticks allowed in the x-axis based on the width
|
||||
// and layout of the chart
|
||||
let ticks = 16;
|
||||
const calculateMaxXTicks = ( width, layout ) => {
|
||||
if ( width < 783 ) {
|
||||
ticks = 7;
|
||||
return 7;
|
||||
} else if ( width >= 783 && width < 1129 ) {
|
||||
ticks = 12;
|
||||
return 12;
|
||||
} else if ( width >= 1130 && width < 1365 ) {
|
||||
if ( layout === 'standard' ) {
|
||||
ticks = 16;
|
||||
return 16;
|
||||
} else if ( layout === 'comparison' ) {
|
||||
ticks = 12;
|
||||
return 12;
|
||||
} else if ( layout === 'compact' ) {
|
||||
ticks = 7;
|
||||
return 7;
|
||||
}
|
||||
} else if ( width >= 1365 ) {
|
||||
if ( layout === 'standard' ) {
|
||||
ticks = 31;
|
||||
return 31;
|
||||
} else if ( layout === 'comparison' ) {
|
||||
ticks = 16;
|
||||
return 16;
|
||||
} else if ( layout === 'compact' ) {
|
||||
ticks = 12;
|
||||
return 12;
|
||||
}
|
||||
}
|
||||
if ( uniqueDates.length <= ticks ) {
|
||||
return uniqueDates;
|
||||
|
||||
return 16;
|
||||
};
|
||||
|
||||
/**
|
||||
* Filter out irrelevant dates so only the first date of each month is kept.
|
||||
* @param {array} dates - string dates.
|
||||
* @returns {array} Filtered dates.
|
||||
*/
|
||||
const getFirstDatePerMonth = dates => {
|
||||
return dates.filter(
|
||||
( date, i ) => i === 0 || new Date( date ).getMonth() !== new Date( dates[ i - 1 ] ).getMonth()
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get x-axis ticks given the unique dates and the increment factor.
|
||||
* @param {array} uniqueDates - all the unique dates from the input data for the chart
|
||||
* @param {integer} incrementFactor - increment factor for the visible ticks.
|
||||
* @returns {array} Ticks for the x-axis.
|
||||
*/
|
||||
const getXTicksFromIncrementFactor = ( uniqueDates, incrementFactor ) => {
|
||||
const ticks = [];
|
||||
|
||||
for ( let idx = 0; idx < uniqueDates.length; idx = idx + incrementFactor ) {
|
||||
ticks.push( uniqueDates[ idx ] );
|
||||
}
|
||||
|
||||
// If the first or last date is missing from the ticks array, add it back in.
|
||||
if ( ticks[ 0 ] !== uniqueDates[ 0 ] ) {
|
||||
ticks.unshift( uniqueDates[ 0 ] );
|
||||
}
|
||||
if ( ticks[ ticks.length - 1 ] !== uniqueDates[ uniqueDates.length - 1 ] ) {
|
||||
ticks.push( uniqueDates[ uniqueDates.length - 1 ] );
|
||||
}
|
||||
|
||||
return ticks;
|
||||
};
|
||||
|
||||
/**
|
||||
* Calculates the increment factor between ticks so there aren't more than maxTicks.
|
||||
* @param {array} uniqueDates - all the unique dates from the input data for the chart
|
||||
* @param {integer} maxTicks - maximum number of ticks that can be displayed in the x-axis
|
||||
* @returns {integer} x-axis ticks increment factor
|
||||
*/
|
||||
const calculateXTicksIncrementFactor = ( uniqueDates, maxTicks ) => {
|
||||
let factors = [];
|
||||
let i = 0;
|
||||
// first we get all the factors of the length of the uniqieDates array
|
||||
let i = 1;
|
||||
// First we get all the factors of the length of the uniqueDates array
|
||||
// if the number is a prime number or near prime (with 3 factors) then we
|
||||
// step down by 1 integer and try again
|
||||
// step down by 1 integer and try again.
|
||||
while ( factors.length <= 3 ) {
|
||||
factors = getFactors( uniqueDates.length - ( 1 + i ) );
|
||||
factors = getFactors( uniqueDates.length - i );
|
||||
i += 1;
|
||||
}
|
||||
let newTicks = [];
|
||||
let factorIndex = 0;
|
||||
// newTicks is the first tick plus the smallest factor (initiallY) etc.
|
||||
// however, if we still end up with too many ticks we look at the next factor
|
||||
// and try again unttil we have fewer ticks than the max
|
||||
while ( newTicks.length > ticks || newTicks.length === 0 ) {
|
||||
if ( newTicks.length > ticks ) {
|
||||
factorIndex += 1;
|
||||
newTicks = [];
|
||||
}
|
||||
for ( let idx = 0; idx < uniqueDates.length; idx = idx + factors[ factorIndex ] ) {
|
||||
newTicks.push( uniqueDates[ idx ] );
|
||||
}
|
||||
|
||||
return factors.find( f => uniqueDates.length / f < maxTicks );
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns ticks for the x-axis.
|
||||
* @param {array} uniqueDates - all the unique dates from the input data for the chart
|
||||
* @param {integer} width - calculated page width
|
||||
* @param {string} layout - standard, comparison or compact chart types
|
||||
* @param {string} interval - string of the interval used in the graph (hour, day, week...)
|
||||
* @returns {integer} number of x-axis ticks based on width and chart layout
|
||||
*/
|
||||
export const getXTicks = ( uniqueDates, width, layout, interval ) => {
|
||||
const maxTicks = calculateMaxXTicks( width, layout );
|
||||
|
||||
if ( uniqueDates.length >= dayTicksThreshold && interval === 'day' ) {
|
||||
uniqueDates = getFirstDatePerMonth( uniqueDates );
|
||||
}
|
||||
// if, for some reason, the first or last date is missing from the newTicks array, add it back in
|
||||
if ( newTicks[ 0 ] !== uniqueDates[ 0 ] ) {
|
||||
newTicks.unshift( uniqueDates[ 0 ] );
|
||||
if ( uniqueDates.length <= maxTicks ) {
|
||||
return uniqueDates;
|
||||
}
|
||||
if ( newTicks[ newTicks.length - 1 ] !== uniqueDates[ uniqueDates.length - 1 ] ) {
|
||||
newTicks.push( uniqueDates[ uniqueDates.length - 1 ] );
|
||||
}
|
||||
return newTicks;
|
||||
|
||||
const incrementFactor = calculateXTicksIncrementFactor( uniqueDates, maxTicks );
|
||||
|
||||
return getXTicksFromIncrementFactor( uniqueDates, incrementFactor );
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -316,7 +360,7 @@ export const drawAxis = ( node, params ) => {
|
|||
.append( 'g' )
|
||||
.attr( 'class', 'axis' )
|
||||
.attr( 'aria-hidden', 'true' )
|
||||
.attr( 'transform', `translate(0,${ params.height })` )
|
||||
.attr( 'transform', `translate(0, ${ params.height })` )
|
||||
.call(
|
||||
d3AxisBottom( xScale )
|
||||
.tickValues( ticks )
|
||||
|
@ -327,7 +371,7 @@ export const drawAxis = ( node, params ) => {
|
|||
.append( 'g' )
|
||||
.attr( 'class', 'axis axis-month' )
|
||||
.attr( 'aria-hidden', 'true' )
|
||||
.attr( 'transform', `translate(3, ${ params.height + 20 })` )
|
||||
.attr( 'transform', `translate(0, ${ params.height + 20 })` )
|
||||
.call(
|
||||
d3AxisBottom( xScale )
|
||||
.tickValues( ticks )
|
||||
|
@ -335,9 +379,7 @@ export const drawAxis = ( node, params ) => {
|
|||
const monthDate = d instanceof Date ? d : new Date( d );
|
||||
let prevMonth = i !== 0 ? ticks[ i - 1 ] : ticks[ i ];
|
||||
prevMonth = prevMonth instanceof Date ? prevMonth : new Date( prevMonth );
|
||||
return monthDate.getDate() === 1 ||
|
||||
i === 0 ||
|
||||
params.x2Format( monthDate ) !== params.x2Format( prevMonth )
|
||||
return i === 0 || params.x2Format( monthDate ) !== params.x2Format( prevMonth )
|
||||
? params.x2Format( monthDate )
|
||||
: '';
|
||||
} )
|
||||
|
|
|
@ -389,14 +389,17 @@ export function getIntervalForQuery( query ) {
|
|||
return current;
|
||||
}
|
||||
|
||||
export const dayTicksThreshold = 180;
|
||||
|
||||
/**
|
||||
* Returns date formats for the current interval.
|
||||
* See https://github.com/d3/d3-time-format for chart formats.
|
||||
*
|
||||
* @param {String} interval Interval to get date formats for.
|
||||
* @param {Int} [ticks] Number of ticks the axis will have.
|
||||
* @return {String} Current interval.
|
||||
*/
|
||||
export function getDateFormatsForInterval( interval ) {
|
||||
export function getDateFormatsForInterval( interval, ticks = 0 ) {
|
||||
let pointLabelFormat = 'F j, Y';
|
||||
let tooltipFormat = '%B %d %Y';
|
||||
let xFormat = '%Y-%m-%d';
|
||||
|
@ -411,7 +414,12 @@ export function getDateFormatsForInterval( interval ) {
|
|||
tableFormat = 'h A';
|
||||
break;
|
||||
case 'day':
|
||||
xFormat = '%d';
|
||||
if ( ticks < dayTicksThreshold ) {
|
||||
xFormat = '%d';
|
||||
} else {
|
||||
xFormat = '%b';
|
||||
x2Format = '%Y';
|
||||
}
|
||||
break;
|
||||
case 'week':
|
||||
xFormat = '%d';
|
||||
|
|
Loading…
Reference in New Issue