Split D3Chart utils axis file (https://github.com/woocommerce/woocommerce-admin/pull/2000)
This commit is contained in:
parent
753780bf7b
commit
dbf0a8d169
226
plugins/woocommerce-admin/packages/components/src/chart/d3chart/utils/axis-x.js
vendored
Normal file
226
plugins/woocommerce-admin/packages/components/src/chart/d3chart/utils/axis-x.js
vendored
Normal file
|
@ -0,0 +1,226 @@
|
||||||
|
/** @format */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* External dependencies
|
||||||
|
*/
|
||||||
|
import { axisBottom as d3AxisBottom } from 'd3-axis';
|
||||||
|
import { smallBreak, wideBreak } from './breakpoints';
|
||||||
|
import moment from 'moment';
|
||||||
|
|
||||||
|
const dayTicksThreshold = 63;
|
||||||
|
const weekTicksThreshold = 9;
|
||||||
|
const mediumBreak = 1130;
|
||||||
|
const smallPoints = 7;
|
||||||
|
const mediumPoints = 12;
|
||||||
|
const largePoints = 16;
|
||||||
|
const mostPoints = 31;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate the maximum number of ticks allowed in the x-axis based on the width and mode of the chart
|
||||||
|
* @param {integer} width - calculated page width
|
||||||
|
* @param {string} mode - item-comparison or time-comparison
|
||||||
|
* @returns {integer} number of x-axis ticks based on width and chart mode
|
||||||
|
*/
|
||||||
|
const calculateMaxXTicks = ( width, mode ) => {
|
||||||
|
if ( width < smallBreak ) {
|
||||||
|
return smallPoints;
|
||||||
|
} else if ( width >= smallBreak && width <= mediumBreak ) {
|
||||||
|
return mediumPoints;
|
||||||
|
} else if ( width > mediumBreak && width <= wideBreak ) {
|
||||||
|
if ( mode === 'time-comparison' ) {
|
||||||
|
return largePoints;
|
||||||
|
} else if ( mode === 'item-comparison' ) {
|
||||||
|
return mediumPoints;
|
||||||
|
}
|
||||||
|
} else if ( width > wideBreak ) {
|
||||||
|
if ( mode === 'time-comparison' ) {
|
||||||
|
return mostPoints;
|
||||||
|
} else if ( mode === 'item-comparison' ) {
|
||||||
|
return largePoints;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return largePoints;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 || moment( date ).toDate().getMonth() !== moment( dates[ i - 1 ] ).toDate().getMonth()
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given an array of dates, returns true if the first and last one belong to the same day.
|
||||||
|
* @param {array} dates - an array of dates
|
||||||
|
* @returns {boolean} whether the first and last date are different hours from the same date.
|
||||||
|
*/
|
||||||
|
const areDatesInTheSameDay = dates => {
|
||||||
|
const firstDate = moment( dates [ 0 ] ).toDate();
|
||||||
|
const lastDate = moment( dates [ dates.length - 1 ] ).toDate();
|
||||||
|
return (
|
||||||
|
firstDate.getDate() === lastDate.getDate() &&
|
||||||
|
firstDate.getMonth() === lastDate.getMonth() &&
|
||||||
|
firstDate.getFullYear() === lastDate.getFullYear()
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Describes `smallestFactor`
|
||||||
|
* @param {number} inputNum - any double or integer
|
||||||
|
* @returns {integer} smallest factor of num
|
||||||
|
*/
|
||||||
|
const getFactors = inputNum => {
|
||||||
|
const numFactors = [];
|
||||||
|
for ( let i = 1; i <= Math.floor( Math.sqrt( inputNum ) ); i++ ) {
|
||||||
|
if ( inputNum % i === 0 ) {
|
||||||
|
numFactors.push( i );
|
||||||
|
inputNum / i !== i && numFactors.push( inputNum / i );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
numFactors.sort( ( x, y ) => x - y ); // numeric sort
|
||||||
|
|
||||||
|
return numFactors;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 = 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.
|
||||||
|
while ( factors.length <= 3 ) {
|
||||||
|
factors = getFactors( uniqueDates.length - i );
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return factors.find( f => uniqueDates.length / f < maxTicks );
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 date is missing from the ticks array, add it back in.
|
||||||
|
if ( ticks[ 0 ] !== uniqueDates[ 0 ] ) {
|
||||||
|
ticks.unshift( uniqueDates[ 0 ] );
|
||||||
|
}
|
||||||
|
|
||||||
|
return ticks;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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} mode - item-comparison or time-comparison
|
||||||
|
* @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 mode
|
||||||
|
*/
|
||||||
|
export const getXTicks = ( uniqueDates, width, mode, interval ) => {
|
||||||
|
const maxTicks = calculateMaxXTicks( width, mode );
|
||||||
|
|
||||||
|
if (
|
||||||
|
( uniqueDates.length >= dayTicksThreshold && interval === 'day' ) ||
|
||||||
|
( uniqueDates.length >= weekTicksThreshold && interval === 'week' )
|
||||||
|
) {
|
||||||
|
uniqueDates = getFirstDatePerMonth( uniqueDates );
|
||||||
|
}
|
||||||
|
if ( uniqueDates.length <= maxTicks ||
|
||||||
|
( interval === 'hour' && areDatesInTheSameDay( uniqueDates ) && width > smallBreak ) ) {
|
||||||
|
return uniqueDates;
|
||||||
|
}
|
||||||
|
|
||||||
|
const incrementFactor = calculateXTicksIncrementFactor( uniqueDates, maxTicks );
|
||||||
|
|
||||||
|
return getXTicksFromIncrementFactor( uniqueDates, incrementFactor );
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compares 2 strings and returns a list of words that are unique from s2
|
||||||
|
* @param {string} s1 - base string to compare against
|
||||||
|
* @param {string} s2 - string to compare against the base string
|
||||||
|
* @param {string|Object} splitChar - character or RegExp to use to deliminate words
|
||||||
|
* @returns {array} of unique words that appear in s2 but not in s1, the base string
|
||||||
|
*/
|
||||||
|
export const compareStrings = ( s1, s2, splitChar = new RegExp( [ ' |,' ], 'g' ) ) => {
|
||||||
|
const string1 = s1.split( splitChar );
|
||||||
|
const string2 = s2.split( splitChar );
|
||||||
|
const diff = new Array();
|
||||||
|
const long = s1.length > s2.length ? string1 : string2;
|
||||||
|
for ( let x = 0; x < long.length; x++ ) {
|
||||||
|
string1[ x ] !== string2[ x ] && diff.push( string2[ x ] );
|
||||||
|
}
|
||||||
|
return diff;
|
||||||
|
};
|
||||||
|
|
||||||
|
const removeDuplicateDates = ( d, i, ticks, formatter ) => {
|
||||||
|
const monthDate = moment( d ).toDate();
|
||||||
|
let prevMonth = i !== 0 ? ticks[ i - 1 ] : ticks[ i ];
|
||||||
|
prevMonth = prevMonth instanceof Date ? prevMonth : moment( prevMonth ).toDate();
|
||||||
|
return i === 0
|
||||||
|
? formatter( monthDate )
|
||||||
|
: compareStrings( formatter( prevMonth ), formatter( monthDate ) ).join( ' ' );
|
||||||
|
};
|
||||||
|
|
||||||
|
export const drawXAxis = ( node, params, scales, formats ) => {
|
||||||
|
const height = scales.yScale.range()[ 0 ];
|
||||||
|
let ticks = getXTicks( params.uniqueDates, scales.xScale.range()[ 1 ], params.mode, params.interval );
|
||||||
|
if ( params.chartType === 'line' ) {
|
||||||
|
ticks = ticks.map( d => moment( d ).toDate() );
|
||||||
|
}
|
||||||
|
|
||||||
|
node
|
||||||
|
.append( 'g' )
|
||||||
|
.attr( 'class', 'axis' )
|
||||||
|
.attr( 'aria-hidden', 'true' )
|
||||||
|
.attr( 'transform', `translate(0, ${ height })` )
|
||||||
|
.call(
|
||||||
|
d3AxisBottom( scales.xScale )
|
||||||
|
.tickValues( ticks )
|
||||||
|
.tickFormat( ( d, i ) => params.interval === 'hour'
|
||||||
|
? formats.xFormat( d instanceof Date ? d : moment( d ).toDate() )
|
||||||
|
: removeDuplicateDates( d, i, ticks, formats.xFormat ) )
|
||||||
|
);
|
||||||
|
|
||||||
|
node
|
||||||
|
.append( 'g' )
|
||||||
|
.attr( 'class', 'axis axis-month' )
|
||||||
|
.attr( 'aria-hidden', 'true' )
|
||||||
|
.attr( 'transform', `translate(0, ${ height + 14 })` )
|
||||||
|
.call(
|
||||||
|
d3AxisBottom( scales.xScale )
|
||||||
|
.tickValues( ticks )
|
||||||
|
.tickFormat( ( d, i ) => removeDuplicateDates( d, i, ticks, formats.x2Format ) )
|
||||||
|
);
|
||||||
|
|
||||||
|
node
|
||||||
|
.append( 'g' )
|
||||||
|
.attr( 'class', 'pipes' )
|
||||||
|
.attr( 'transform', `translate(0, ${ height })` )
|
||||||
|
.call(
|
||||||
|
d3AxisBottom( scales.xScale )
|
||||||
|
.tickValues( ticks )
|
||||||
|
.tickSize( 5 )
|
||||||
|
.tickFormat( '' )
|
||||||
|
);
|
||||||
|
};
|
76
plugins/woocommerce-admin/packages/components/src/chart/d3chart/utils/axis-y.js
vendored
Normal file
76
plugins/woocommerce-admin/packages/components/src/chart/d3chart/utils/axis-y.js
vendored
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
/** @format */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* External dependencies
|
||||||
|
*/
|
||||||
|
import { axisLeft as d3AxisLeft } from 'd3-axis';
|
||||||
|
|
||||||
|
const calculateYGridValues = ( numberOfTicks, limit, roundValues ) => {
|
||||||
|
const grids = [];
|
||||||
|
|
||||||
|
for ( let i = 0; i < numberOfTicks; i++ ) {
|
||||||
|
const val = ( i + 1 ) / numberOfTicks * limit;
|
||||||
|
const rVal = roundValues ? Math.round( val ) : val;
|
||||||
|
if ( grids[ grids.length - 1 ] !== rVal ) {
|
||||||
|
grids.push( rVal );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return grids;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getNegativeYGrids = ( yMin, step ) => {
|
||||||
|
if ( yMin >= 0 ) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const numberOfTicks = Math.ceil( -yMin / step );
|
||||||
|
return calculateYGridValues( numberOfTicks, yMin, yMin < -1 );
|
||||||
|
};
|
||||||
|
|
||||||
|
const getPositiveYGrids = ( yMax, step ) => {
|
||||||
|
if ( yMax <= 0 ) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const numberOfTicks = Math.ceil( yMax / step );
|
||||||
|
return calculateYGridValues( numberOfTicks, yMax, yMax > 1 );
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getYGrids = ( yMin, yMax, step ) => {
|
||||||
|
return [
|
||||||
|
0,
|
||||||
|
...getNegativeYGrids( yMin, step ),
|
||||||
|
...getPositiveYGrids( yMax, step ),
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const drawYAxis = ( node, scales, formats, margin, isRTL ) => {
|
||||||
|
const yGrids = getYGrids( scales.yScale.domain()[ 0 ], scales.yScale.domain()[ 1 ], scales.step );
|
||||||
|
const width = scales.xScale.range()[ 1 ];
|
||||||
|
const xPosition = isRTL ? width + margin.left + margin.right / 2 - 15 : -margin.left / 2 - 15;
|
||||||
|
|
||||||
|
const withPositiveValuesClass = scales.yMin >= 0 || scales.yMax > 0 ? ' with-positive-ticks' : '';
|
||||||
|
node
|
||||||
|
.append( 'g' )
|
||||||
|
.attr( 'class', 'grid' + withPositiveValuesClass )
|
||||||
|
.attr( 'transform', `translate(-${ margin.left }, 0)` )
|
||||||
|
.call(
|
||||||
|
d3AxisLeft( scales.yScale )
|
||||||
|
.tickValues( yGrids )
|
||||||
|
.tickSize( -width - margin.left - margin.right )
|
||||||
|
.tickFormat( '' )
|
||||||
|
);
|
||||||
|
|
||||||
|
node
|
||||||
|
.append( 'g' )
|
||||||
|
.attr( 'class', 'axis y-axis' )
|
||||||
|
.attr( 'aria-hidden', 'true' )
|
||||||
|
.attr( 'transform', 'translate(' + xPosition + ', 12)' )
|
||||||
|
.attr( 'text-anchor', 'start' )
|
||||||
|
.call(
|
||||||
|
d3AxisLeft( scales.yScale )
|
||||||
|
.tickValues( scales.yMax === 0 && scales.yMin === 0 ? [ yGrids[ 0 ] ] : yGrids )
|
||||||
|
.tickFormat( d => formats.yFormat( d !== 0 ? d : 0 ) )
|
||||||
|
);
|
||||||
|
};
|
|
@ -3,297 +3,8 @@
|
||||||
/**
|
/**
|
||||||
* External dependencies
|
* External dependencies
|
||||||
*/
|
*/
|
||||||
import { axisBottom as d3AxisBottom, axisLeft as d3AxisLeft } from 'd3-axis';
|
import { drawXAxis } from './axis-x';
|
||||||
import { smallBreak, wideBreak } from './breakpoints';
|
import { drawYAxis } from './axis-y';
|
||||||
import moment from 'moment';
|
|
||||||
|
|
||||||
const dayTicksThreshold = 63;
|
|
||||||
const weekTicksThreshold = 9;
|
|
||||||
const mediumBreak = 1130;
|
|
||||||
const smallPoints = 7;
|
|
||||||
const mediumPoints = 12;
|
|
||||||
const largePoints = 16;
|
|
||||||
const mostPoints = 31;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Describes `smallestFactor`
|
|
||||||
* @param {number} inputNum - any double or integer
|
|
||||||
* @returns {integer} smallest factor of num
|
|
||||||
*/
|
|
||||||
const getFactors = inputNum => {
|
|
||||||
const numFactors = [];
|
|
||||||
for ( let i = 1; i <= Math.floor( Math.sqrt( inputNum ) ); i++ ) {
|
|
||||||
if ( inputNum % i === 0 ) {
|
|
||||||
numFactors.push( i );
|
|
||||||
inputNum / i !== i && numFactors.push( inputNum / i );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
numFactors.sort( ( x, y ) => x - y ); // numeric sort
|
|
||||||
|
|
||||||
return numFactors;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculate the maximum number of ticks allowed in the x-axis based on the width and mode of the chart
|
|
||||||
* @param {integer} width - calculated page width
|
|
||||||
* @param {string} mode - item-comparison or time-comparison
|
|
||||||
* @returns {integer} number of x-axis ticks based on width and chart mode
|
|
||||||
*/
|
|
||||||
const calculateMaxXTicks = ( width, mode ) => {
|
|
||||||
if ( width < smallBreak ) {
|
|
||||||
return smallPoints;
|
|
||||||
} else if ( width >= smallBreak && width <= mediumBreak ) {
|
|
||||||
return mediumPoints;
|
|
||||||
} else if ( width > mediumBreak && width <= wideBreak ) {
|
|
||||||
if ( mode === 'time-comparison' ) {
|
|
||||||
return largePoints;
|
|
||||||
} else if ( mode === 'item-comparison' ) {
|
|
||||||
return mediumPoints;
|
|
||||||
}
|
|
||||||
} else if ( width > wideBreak ) {
|
|
||||||
if ( mode === 'time-comparison' ) {
|
|
||||||
return mostPoints;
|
|
||||||
} else if ( mode === 'item-comparison' ) {
|
|
||||||
return largePoints;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return largePoints;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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 date is missing from the ticks array, add it back in.
|
|
||||||
if ( ticks[ 0 ] !== uniqueDates[ 0 ] ) {
|
|
||||||
ticks.unshift( uniqueDates[ 0 ] );
|
|
||||||
}
|
|
||||||
|
|
||||||
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 = 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.
|
|
||||||
while ( factors.length <= 3 ) {
|
|
||||||
factors = getFactors( uniqueDates.length - i );
|
|
||||||
i += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return factors.find( f => uniqueDates.length / f < maxTicks );
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Given an array of dates, returns true if the first and last one belong to the same day.
|
|
||||||
* @param {array} dates - an array of dates
|
|
||||||
* @returns {boolean} whether the first and last date are different hours from the same date.
|
|
||||||
*/
|
|
||||||
const areDatesInTheSameDay = dates => {
|
|
||||||
const firstDate = moment( dates [ 0 ] ).toDate();
|
|
||||||
const lastDate = moment( dates [ dates.length - 1 ] ).toDate();
|
|
||||||
return (
|
|
||||||
firstDate.getDate() === lastDate.getDate() &&
|
|
||||||
firstDate.getMonth() === lastDate.getMonth() &&
|
|
||||||
firstDate.getFullYear() === lastDate.getFullYear()
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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 || moment( date ).toDate().getMonth() !== moment( dates[ i - 1 ] ).toDate().getMonth()
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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} mode - item-comparison or time-comparison
|
|
||||||
* @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 mode
|
|
||||||
*/
|
|
||||||
export const getXTicks = ( uniqueDates, width, mode, interval ) => {
|
|
||||||
const maxTicks = calculateMaxXTicks( width, mode );
|
|
||||||
|
|
||||||
if (
|
|
||||||
( uniqueDates.length >= dayTicksThreshold && interval === 'day' ) ||
|
|
||||||
( uniqueDates.length >= weekTicksThreshold && interval === 'week' )
|
|
||||||
) {
|
|
||||||
uniqueDates = getFirstDatePerMonth( uniqueDates );
|
|
||||||
}
|
|
||||||
if ( uniqueDates.length <= maxTicks ||
|
|
||||||
( interval === 'hour' && areDatesInTheSameDay( uniqueDates ) && width > smallBreak ) ) {
|
|
||||||
return uniqueDates;
|
|
||||||
}
|
|
||||||
|
|
||||||
const incrementFactor = calculateXTicksIncrementFactor( uniqueDates, maxTicks );
|
|
||||||
|
|
||||||
return getXTicksFromIncrementFactor( uniqueDates, incrementFactor );
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Compares 2 strings and returns a list of words that are unique from s2
|
|
||||||
* @param {string} s1 - base string to compare against
|
|
||||||
* @param {string} s2 - string to compare against the base string
|
|
||||||
* @param {string|Object} splitChar - character or RegExp to use to deliminate words
|
|
||||||
* @returns {array} of unique words that appear in s2 but not in s1, the base string
|
|
||||||
*/
|
|
||||||
export const compareStrings = ( s1, s2, splitChar = new RegExp( [ ' |,' ], 'g' ) ) => {
|
|
||||||
const string1 = s1.split( splitChar );
|
|
||||||
const string2 = s2.split( splitChar );
|
|
||||||
const diff = new Array();
|
|
||||||
const long = s1.length > s2.length ? string1 : string2;
|
|
||||||
for ( let x = 0; x < long.length; x++ ) {
|
|
||||||
string1[ x ] !== string2[ x ] && diff.push( string2[ x ] );
|
|
||||||
}
|
|
||||||
return diff;
|
|
||||||
};
|
|
||||||
|
|
||||||
const calculateYGridValues = ( numberOfTicks, limit, roundValues ) => {
|
|
||||||
const grids = [];
|
|
||||||
|
|
||||||
for ( let i = 0; i < numberOfTicks; i++ ) {
|
|
||||||
const val = ( i + 1 ) / numberOfTicks * limit;
|
|
||||||
const rVal = roundValues ? Math.round( val ) : val;
|
|
||||||
if ( grids[ grids.length - 1 ] !== rVal ) {
|
|
||||||
grids.push( rVal );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return grids;
|
|
||||||
};
|
|
||||||
|
|
||||||
const getNegativeYGrids = ( yMin, step ) => {
|
|
||||||
if ( yMin >= 0 ) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
const numberOfTicks = Math.ceil( -yMin / step );
|
|
||||||
return calculateYGridValues( numberOfTicks, yMin, yMin < -1 );
|
|
||||||
};
|
|
||||||
|
|
||||||
const getPositiveYGrids = ( yMax, step ) => {
|
|
||||||
if ( yMax <= 0 ) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
const numberOfTicks = Math.ceil( yMax / step );
|
|
||||||
return calculateYGridValues( numberOfTicks, yMax, yMax > 1 );
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getYGrids = ( yMin, yMax, step ) => {
|
|
||||||
return [
|
|
||||||
0,
|
|
||||||
...getNegativeYGrids( yMin, step ),
|
|
||||||
...getPositiveYGrids( yMax, step ),
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
const removeDuplicateDates = ( d, i, ticks, formatter ) => {
|
|
||||||
const monthDate = moment( d ).toDate();
|
|
||||||
let prevMonth = i !== 0 ? ticks[ i - 1 ] : ticks[ i ];
|
|
||||||
prevMonth = prevMonth instanceof Date ? prevMonth : moment( prevMonth ).toDate();
|
|
||||||
return i === 0
|
|
||||||
? formatter( monthDate )
|
|
||||||
: compareStrings( formatter( prevMonth ), formatter( monthDate ) ).join( ' ' );
|
|
||||||
};
|
|
||||||
|
|
||||||
const drawXAxis = ( node, params, scales, formats ) => {
|
|
||||||
const height = scales.yScale.range()[ 0 ];
|
|
||||||
let ticks = getXTicks( params.uniqueDates, scales.xScale.range()[ 1 ], params.mode, params.interval );
|
|
||||||
if ( params.chartType === 'line' ) {
|
|
||||||
ticks = ticks.map( d => moment( d ).toDate() );
|
|
||||||
}
|
|
||||||
|
|
||||||
node
|
|
||||||
.append( 'g' )
|
|
||||||
.attr( 'class', 'axis' )
|
|
||||||
.attr( 'aria-hidden', 'true' )
|
|
||||||
.attr( 'transform', `translate(0, ${ height })` )
|
|
||||||
.call(
|
|
||||||
d3AxisBottom( scales.xScale )
|
|
||||||
.tickValues( ticks )
|
|
||||||
.tickFormat( ( d, i ) => params.interval === 'hour'
|
|
||||||
? formats.xFormat( d instanceof Date ? d : moment( d ).toDate() )
|
|
||||||
: removeDuplicateDates( d, i, ticks, formats.xFormat ) )
|
|
||||||
);
|
|
||||||
|
|
||||||
node
|
|
||||||
.append( 'g' )
|
|
||||||
.attr( 'class', 'axis axis-month' )
|
|
||||||
.attr( 'aria-hidden', 'true' )
|
|
||||||
.attr( 'transform', `translate(0, ${ height + 14 })` )
|
|
||||||
.call(
|
|
||||||
d3AxisBottom( scales.xScale )
|
|
||||||
.tickValues( ticks )
|
|
||||||
.tickFormat( ( d, i ) => removeDuplicateDates( d, i, ticks, formats.x2Format ) )
|
|
||||||
);
|
|
||||||
|
|
||||||
node
|
|
||||||
.append( 'g' )
|
|
||||||
.attr( 'class', 'pipes' )
|
|
||||||
.attr( 'transform', `translate(0, ${ height })` )
|
|
||||||
.call(
|
|
||||||
d3AxisBottom( scales.xScale )
|
|
||||||
.tickValues( ticks )
|
|
||||||
.tickSize( 5 )
|
|
||||||
.tickFormat( '' )
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const drawYAxis = ( node, scales, formats, margin, isRTL ) => {
|
|
||||||
const yGrids = getYGrids( scales.yScale.domain()[ 0 ], scales.yScale.domain()[ 1 ], scales.step );
|
|
||||||
const width = scales.xScale.range()[ 1 ];
|
|
||||||
const xPosition = isRTL ? width + margin.left + margin.right / 2 - 15 : -margin.left / 2 - 15;
|
|
||||||
|
|
||||||
const withPositiveValuesClass = scales.yMin >= 0 || scales.yMax > 0 ? ' with-positive-ticks' : '';
|
|
||||||
node
|
|
||||||
.append( 'g' )
|
|
||||||
.attr( 'class', 'grid' + withPositiveValuesClass )
|
|
||||||
.attr( 'transform', `translate(-${ margin.left }, 0)` )
|
|
||||||
.call(
|
|
||||||
d3AxisLeft( scales.yScale )
|
|
||||||
.tickValues( yGrids )
|
|
||||||
.tickSize( -width - margin.left - margin.right )
|
|
||||||
.tickFormat( '' )
|
|
||||||
);
|
|
||||||
|
|
||||||
node
|
|
||||||
.append( 'g' )
|
|
||||||
.attr( 'class', 'axis y-axis' )
|
|
||||||
.attr( 'aria-hidden', 'true' )
|
|
||||||
.attr( 'transform', 'translate(' + xPosition + ', 12)' )
|
|
||||||
.attr( 'text-anchor', 'start' )
|
|
||||||
.call(
|
|
||||||
d3AxisLeft( scales.yScale )
|
|
||||||
.tickValues( scales.yMax === 0 && scales.yMin === 0 ? [ yGrids[ 0 ] ] : yGrids )
|
|
||||||
.tickFormat( d => formats.yFormat( d !== 0 ? d : 0 ) )
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const drawAxis = ( node, params, scales, formats, margin, isRTL ) => {
|
export const drawAxis = ( node, params, scales, formats, margin, isRTL ) => {
|
||||||
drawXAxis( node, params, scales, formats );
|
drawXAxis( node, params, scales, formats );
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
/**
|
/**
|
||||||
* Internal dependencies
|
* Internal dependencies
|
||||||
*/
|
*/
|
||||||
import { compareStrings, getXTicks, getYGrids } from '../axis';
|
import { compareStrings, getXTicks } from '../axis-x';
|
||||||
|
|
||||||
describe( 'getXTicks', () => {
|
describe( 'getXTicks', () => {
|
||||||
describe( 'interval=day', () => {
|
describe( 'interval=day', () => {
|
||||||
|
@ -238,59 +238,3 @@ describe( 'compareStrings', () => {
|
||||||
expect( compareStrings( 'Jul, 2018', 'Aug, 2018' ).join( ' ' ) ).toEqual( 'Aug' );
|
expect( compareStrings( 'Jul, 2018', 'Aug, 2018' ).join( ' ' ) ).toEqual( 'Aug' );
|
||||||
} );
|
} );
|
||||||
} );
|
} );
|
||||||
|
|
||||||
describe( 'getYGrids', () => {
|
|
||||||
it( 'returns a single 0 when yMax and yMin are 0', () => {
|
|
||||||
expect( getYGrids( 0, 0, 0 ) ).toEqual( [ 0 ] );
|
|
||||||
} );
|
|
||||||
|
|
||||||
describe( 'positive charts', () => {
|
|
||||||
it( 'returns decimal values when yMax is <= 1 and yMin is 0', () => {
|
|
||||||
expect( getYGrids( 0, 1, 0.3333333333333333 ) ).toEqual( [ 0, 0.3333333333333333, 0.6666666666666666, 1 ] );
|
|
||||||
} );
|
|
||||||
|
|
||||||
it( 'returns decimal values when yMax and yMin are <= 1', () => {
|
|
||||||
expect( getYGrids( 1, 1, 0.3333333333333333 ) ).toEqual( [ 0, 0.3333333333333333, 0.6666666666666666, 1 ] );
|
|
||||||
} );
|
|
||||||
|
|
||||||
it( 'doesn\'t return decimal values when yMax is > 1', () => {
|
|
||||||
expect( getYGrids( 0, 2, 1 ) ).toEqual( [ 0, 1, 2 ] );
|
|
||||||
} );
|
|
||||||
|
|
||||||
it( 'returns up to four values when yMax is a big number', () => {
|
|
||||||
expect( getYGrids( 0, 12000, 4000 ) ).toEqual( [ 0, 4000, 8000, 12000 ] );
|
|
||||||
} );
|
|
||||||
} );
|
|
||||||
|
|
||||||
describe( 'negative charts', () => {
|
|
||||||
it( 'returns decimal values when yMin is >= -1 and yMax is 0', () => {
|
|
||||||
expect( getYGrids( -1, 0, 0.3333333333333333 ) ).toEqual( [ 0, -0.3333333333333333, -0.6666666666666666, -1 ] );
|
|
||||||
} );
|
|
||||||
|
|
||||||
it( 'returns decimal values when yMax and yMin are >= -1', () => {
|
|
||||||
expect( getYGrids( -1, -1, 0.3333333333333333 ) ).toEqual( [ 0, -0.3333333333333333, -0.6666666666666666, -1 ] );
|
|
||||||
} );
|
|
||||||
|
|
||||||
it( 'doesn\'t return decimal values when yMin is < -1', () => {
|
|
||||||
expect( getYGrids( -2, 0, 1 ) ).toEqual( [ 0, -1, -2 ] );
|
|
||||||
} );
|
|
||||||
|
|
||||||
it( 'returns up to four values when yMin is a big negative number', () => {
|
|
||||||
expect( getYGrids( -12000, 0, 4000 ) ).toEqual( [ 0, -4000, -8000, -12000 ] );
|
|
||||||
} );
|
|
||||||
} );
|
|
||||||
|
|
||||||
describe( 'positive & negative charts', () => {
|
|
||||||
it( 'returns decimal values when yMax is <= 1 and yMin is 0', () => {
|
|
||||||
expect( getYGrids( -1, 1, 0.5 ) ).toEqual( [ 0, -0.5, -1, 0.5, 1 ] );
|
|
||||||
} );
|
|
||||||
|
|
||||||
it( 'doesn\'t return decimal values when yMax is > 1', () => {
|
|
||||||
expect( getYGrids( -2, 2, 1 ) ).toEqual( [ 0, -1, -2, 1, 2 ] );
|
|
||||||
} );
|
|
||||||
|
|
||||||
it( 'returns up to six values when yMax is a big number', () => {
|
|
||||||
expect( getYGrids( -12000, 12000, 6000 ) ).toEqual( [ 0, -6000, -12000, 6000, 12000 ] );
|
|
||||||
} );
|
|
||||||
} );
|
|
||||||
} );
|
|
65
plugins/woocommerce-admin/packages/components/src/chart/d3chart/utils/test/axis-y.js
vendored
Normal file
65
plugins/woocommerce-admin/packages/components/src/chart/d3chart/utils/test/axis-y.js
vendored
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
/** @format */
|
||||||
|
/**
|
||||||
|
* External dependencies
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal dependencies
|
||||||
|
*/
|
||||||
|
import { getYGrids } from '../axis-y';
|
||||||
|
|
||||||
|
describe( 'getYGrids', () => {
|
||||||
|
it( 'returns a single 0 when yMax and yMin are 0', () => {
|
||||||
|
expect( getYGrids( 0, 0, 0 ) ).toEqual( [ 0 ] );
|
||||||
|
} );
|
||||||
|
|
||||||
|
describe( 'positive charts', () => {
|
||||||
|
it( 'returns decimal values when yMax is <= 1 and yMin is 0', () => {
|
||||||
|
expect( getYGrids( 0, 1, 0.3333333333333333 ) ).toEqual( [ 0, 0.3333333333333333, 0.6666666666666666, 1 ] );
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( 'returns decimal values when yMax and yMin are <= 1', () => {
|
||||||
|
expect( getYGrids( 1, 1, 0.3333333333333333 ) ).toEqual( [ 0, 0.3333333333333333, 0.6666666666666666, 1 ] );
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( 'doesn\'t return decimal values when yMax is > 1', () => {
|
||||||
|
expect( getYGrids( 0, 2, 1 ) ).toEqual( [ 0, 1, 2 ] );
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( 'returns up to four values when yMax is a big number', () => {
|
||||||
|
expect( getYGrids( 0, 12000, 4000 ) ).toEqual( [ 0, 4000, 8000, 12000 ] );
|
||||||
|
} );
|
||||||
|
} );
|
||||||
|
|
||||||
|
describe( 'negative charts', () => {
|
||||||
|
it( 'returns decimal values when yMin is >= -1 and yMax is 0', () => {
|
||||||
|
expect( getYGrids( -1, 0, 0.3333333333333333 ) ).toEqual( [ 0, -0.3333333333333333, -0.6666666666666666, -1 ] );
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( 'returns decimal values when yMax and yMin are >= -1', () => {
|
||||||
|
expect( getYGrids( -1, -1, 0.3333333333333333 ) ).toEqual( [ 0, -0.3333333333333333, -0.6666666666666666, -1 ] );
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( 'doesn\'t return decimal values when yMin is < -1', () => {
|
||||||
|
expect( getYGrids( -2, 0, 1 ) ).toEqual( [ 0, -1, -2 ] );
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( 'returns up to four values when yMin is a big negative number', () => {
|
||||||
|
expect( getYGrids( -12000, 0, 4000 ) ).toEqual( [ 0, -4000, -8000, -12000 ] );
|
||||||
|
} );
|
||||||
|
} );
|
||||||
|
|
||||||
|
describe( 'positive & negative charts', () => {
|
||||||
|
it( 'returns decimal values when yMax is <= 1 and yMin is 0', () => {
|
||||||
|
expect( getYGrids( -1, 1, 0.5 ) ).toEqual( [ 0, -0.5, -1, 0.5, 1 ] );
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( 'doesn\'t return decimal values when yMax is > 1', () => {
|
||||||
|
expect( getYGrids( -2, 2, 1 ) ).toEqual( [ 0, -1, -2, 1, 2 ] );
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( 'returns up to six values when yMax is a big number', () => {
|
||||||
|
expect( getYGrids( -12000, 12000, 6000 ) ).toEqual( [ 0, -6000, -12000, 6000, 12000 ] );
|
||||||
|
} );
|
||||||
|
} );
|
||||||
|
} );
|
Loading…
Reference in New Issue