Merge pull request woocommerce/woocommerce-admin#270 from woocommerce/add/chart-card-components

D3 Chart Component: split example and index

Managed to address https://github.com/woocommerce/wc-admin/pull/270#issuecomment-412011428 in this PR and did a bit of additional refactoring.
This commit is contained in:
Robert Elliott 2018-08-13 12:42:47 +02:00 committed by GitHub
commit 09f33dd1fa
11 changed files with 3967 additions and 3887 deletions

View File

@ -3,9 +3,8 @@
/**
* External dependencies
*/
import React from 'react';
import { Component } from '@wordpress/element';
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';
@ -21,7 +20,6 @@ import {
drawAxis,
drawBars,
drawLines,
getColorScale,
getDateSpaces,
getOrderedKeys,
getLine,
@ -37,52 +35,33 @@ import {
} from './utils';
class D3Chart extends Component {
static propTypes = {
className: PropTypes.string,
data: PropTypes.array.isRequired,
height: PropTypes.number,
legend: PropTypes.array,
margin: PropTypes.shape( {
bottom: PropTypes.number,
left: PropTypes.number,
right: PropTypes.number,
top: PropTypes.number,
} ),
orderedKeys: PropTypes.array,
type: PropTypes.oneOf( [ 'bar', 'line' ] ),
width: PropTypes.number,
xFormat: PropTypes.string,
yFormat: PropTypes.string,
};
constructor( props ) {
super( props );
this.getAllData = this.getAllData.bind( this );
this.state = {
allData: this.getAllData( props ),
width: props.width,
};
this.tooltipRef = createRef();
}
static defaultProps = {
height: 200,
margin: {
bottom: 30,
left: 40,
right: 0,
top: 20,
},
type: 'line',
width: 600,
xFormat: '%Y-%m-%d',
yFormat: ',.0f',
};
state = {
allData: null,
};
tooltipRef = React.createRef();
static getDerivedStateFromProps( nextProps, prevState ) {
const nextAllData = [ ...nextProps.data, ...nextProps.orderedKeys ];
if ( prevState.allData !== nextAllData ) {
return { allData: nextAllData };
componentDidUpdate( prevProps, prevState ) {
const { width } = this.props;
/* eslint-disable react/no-did-update-set-state */
if ( width !== prevProps.width ) {
this.setState( { width } );
}
const nextAllData = this.getAllData( this.props );
if ( ! isEqual( [ ...nextAllData ].sort(), [ ...prevState.allData ].sort() ) ) {
this.setState( { allData: nextAllData } );
}
/* eslint-enable react/no-did-update-set-state */
}
return null;
getAllData( props ) {
const orderedKeys =
props.orderedKeys || getOrderedKeys( props.data, getUniqueKeys( props.data ) );
return [ ...props.data, ...orderedKeys ];
}
drawChart = ( node, params ) => {
@ -97,8 +76,7 @@ class D3Chart extends Component {
width: params.width - margin.left - margin.right,
tooltip: d3Select( this.tooltipRef.current ),
} );
drawAxis( g, data, adjParams );
drawAxis( g, adjParams );
type === 'line' && drawLines( g, data, adjParams );
type === 'bar' && drawBars( g, data, adjParams );
@ -106,25 +84,26 @@ class D3Chart extends Component {
};
getParams = node => {
const { data, height, margin, orderedKeys, type, width, xFormat, yFormat } = this.props;
const { colorScheme, data, height, margin, orderedKeys, type, xFormat, yFormat } = this.props;
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 );
const newOrderedKeys = orderedKeys ? orderedKeys : getOrderedKeys( data, uniqueKeys );
const lineData = getLineData( data, orderedKeys );
const newOrderedKeys = orderedKeys || getOrderedKeys( data, uniqueKeys );
const lineData = getLineData( data, newOrderedKeys );
const yMax = getYMax( lineData );
const yScale = getYScale( adjHeight, yMax );
const uniqueDates = getUniqueDates( lineData );
const xLineScale = getXLineScale( uniqueDates, adjWidth );
const xScale = getXScale( uniqueDates, adjWidth );
return {
colorScale: getColorScale( orderedKeys ),
colorScheme,
dateSpaces: getDateSpaces( uniqueDates, adjWidth, xLineScale ),
height: calculatedHeight,
line: getLine( data, xLineScale, yScale ),
line: getLine( xLineScale, yScale ),
lineData,
margin,
orderedKeys: newOrderedKeys,
@ -148,14 +127,17 @@ class D3Chart extends Component {
if ( ! this.props.data ) {
return null; // TODO: improve messaging
}
return (
<div className={ classNames( 'woocommerce-chart__container', this.props.className ) }>
<div
className={ classNames( 'woocommerce-chart__container', this.props.className ) }
style={ { height: this.props.height } }
>
<D3Base
className={ classNames( this.props.className ) }
data={ this.state.allData }
drawChart={ this.drawChart }
getParams={ this.getParams }
width={ this.state.width }
/>
<div className="tooltip" ref={ this.tooltipRef } />
</div>
@ -163,4 +145,38 @@ class D3Chart extends Component {
}
}
D3Chart.propTypes = {
colorScheme: PropTypes.func,
className: PropTypes.string,
data: PropTypes.array.isRequired,
height: PropTypes.number,
legend: PropTypes.array,
margin: PropTypes.shape( {
bottom: PropTypes.number,
left: PropTypes.number,
right: PropTypes.number,
top: PropTypes.number,
} ),
orderedKeys: PropTypes.array,
type: PropTypes.oneOf( [ 'bar', 'line' ] ),
width: PropTypes.number,
xFormat: PropTypes.string,
yFormat: PropTypes.string,
};
D3Chart.defaultProps = {
data: [],
height: 200,
margin: {
bottom: 30,
left: 40,
right: 0,
top: 20,
},
type: 'line',
width: 600,
xFormat: '%Y-%m-%d',
yFormat: ',.0f',
};
export default D3Chart;

View File

@ -5,8 +5,8 @@
*/
import classNames from 'classnames';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { isEmpty } from 'lodash';
import { Component, createRef } from '@wordpress/element';
import { isEmpty, isEqual } from 'lodash';
import { select as d3Select } from 'd3-selection';
/**
@ -38,25 +38,30 @@ export default class D3Base extends Component {
params: null,
drawChart: null,
getParams: null,
width: null,
};
chartRef = React.createRef();
chartRef = createRef();
static getDerivedStateFromProps( nextProps, prevState ) {
let state = {};
if ( nextProps.data !== prevState.data ) {
if ( ! isEqual( nextProps.data, prevState.data ) ) {
state = { ...state, data: nextProps.data };
}
if ( nextProps.drawChart !== prevState.drawChart ) {
if ( ! isEqual( nextProps.drawChart, prevState.drawChart ) ) {
state = { ...state, drawChart: nextProps.drawChart };
}
if ( nextProps.getParams !== prevState.getParams ) {
if ( ! isEqual( nextProps.getParams, prevState.getParams ) ) {
state = { ...state, getParams: nextProps.getParams };
}
if ( nextProps.width !== prevState.width ) {
state = { ...state, width: nextProps.width };
}
if ( ! isEmpty( state ) ) {
return { ...state, params: null };
}
@ -72,8 +77,9 @@ export default class D3Base extends Component {
shouldComponentUpdate( nextProps, nextState ) {
return (
( nextState.params !== null && this.state.params !== nextState.params ) ||
this.state.data !== nextState.data
( nextState.params !== null && ! isEqual( this.state.params, nextState.params ) ) ||
! isEqual( this.state.data, nextState.data ) ||
this.state.width !== nextState.width
);
}

View File

@ -0,0 +1,103 @@
/** @format */
/**
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import { Component, Fragment } from '@wordpress/element';
/**
* Internal dependencies
*/
import Card from 'components/card';
import Chart from 'components/chart';
import dummyOrders from './test/fixtures/dummy';
class WidgetCharts extends Component {
constructor() {
super( ...arguments );
this.handleChange = this.handleChange.bind( this );
this.getSomeOrders = this.getSomeOrders.bind( this );
const products = [
{
key: 'date',
selected: true,
},
{
key: 'Cap',
selected: true,
},
{
key: 'T-Shirt',
selected: true,
},
{
key: 'Sunglasses',
selected: true,
},
{
key: 'Polo',
selected: true,
},
{
key: 'Hoodie',
selected: true,
},
];
const someOrders = this.getSomeOrders( products );
this.state = {
products,
someOrders,
};
}
getSomeOrders( products ) {
return dummyOrders.map( d => {
return Object.keys( d )
.filter( key =>
products
.filter( k => k.selected )
.map( k => k.key )
.includes( key )
)
.reduce( ( accum, current ) => ( { ...accum, [ current ]: d[ current ] } ), {} );
} );
}
handleChange( event ) {
const products = this.state.products.map( d => ( {
...d,
selected: d.key === event.target.id ? ! d.selected : d.selected,
} ) );
const someOrders = this.getSomeOrders( products );
this.setState( { products, someOrders } );
}
render() {
return (
<Fragment>
<Card title={ __( 'Test Categories', 'wc-admin' ) }>
<ul>
{ this.state.products.map( d => (
<li key={ d.key } style={ { display: 'inline', marginRight: '12px' } }>
<label htmlFor={ d.key }>
<input
id={ d.key }
type="checkbox"
onChange={ this.handleChange }
checked={ d.selected }
/>{' '}
{ d.key }
</label>
</li>
) ) }
</ul>
</Card>
<Card title={ __( 'Store Charts', 'wc-admin' ) }>
<Chart data={ this.state.someOrders } title="Example Chart" />
</Card>
</Fragment>
);
}
}
export default WidgetCharts;

View File

@ -2,79 +2,69 @@
/**
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import classNames from 'classnames';
import { Component, Fragment } from '@wordpress/element';
import { isEqual } from 'lodash';
import { Component, createRef } from '@wordpress/element';
import PropTypes from 'prop-types';
import { interpolateViridis as d3InterpolateViridis } from 'd3-scale-chromatic';
/**
* Internal dependencies
*/
import Card from 'components/card';
import D3Chart from './charts';
import dummyOrders from './test/fixtures/dummy';
import Legend from './legend';
import { gap, gaplarge } from 'stylesheets/abstracts/_variables.scss';
const WIDE_BREAKPOINT = 1130;
const WIDE_BREAKPOINT = 1100;
class WidgetCharts extends Component {
constructor() {
super( ...arguments );
this.getOrderedKeys = this.getOrderedKeys.bind( this );
this.handleLegendHover = this.handleLegendHover.bind( this );
this.handleLegendToggle = this.handleLegendToggle.bind( this );
this.updateDimensions = this.updateDimensions.bind( this );
this.handleChange = this.handleChange.bind( this );
this.getSomeOrders = this.getSomeOrders.bind( this );
const products = [
{
key: 'date',
selected: true,
},
{
key: 'Cap',
selected: true,
},
{
key: 'T-Shirt',
selected: true,
},
{
key: 'Sunglasses',
selected: true,
},
{
key: 'Polo',
selected: true,
},
{
key: 'Hoodie',
selected: true,
},
];
const someOrders = this.getSomeOrders( products );
function getOrderedKeys( data ) {
return [
...new Set(
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 );
}
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.state = {
products,
orderedKeys: this.getOrderedKeys( someOrders ).map( d => ( {
...d,
visible: true,
focus: true,
} ) ),
someOrders,
bodyWidth: document.getElementById( 'wpbody' ).getBoundingClientRect().width,
data: props.data,
orderedKeys: getOrderedKeys( props.data ),
visibleData: [ ...props.data ],
width: wpBody - 2 * calcGap,
};
this.handleLegendToggle = this.handleLegendToggle.bind( this );
this.handleLegendHover = this.handleLegendHover.bind( this );
this.updateDimensions = this.updateDimensions.bind( this );
this.getVisibleData = this.getVisibleData.bind( this );
}
getSomeOrders( products ) {
return dummyOrders.map( d => {
return Object.keys( d )
.filter( key =>
products
.filter( k => k.selected )
.map( k => k.key )
.includes( key )
)
.reduce( ( accum, current ) => ( { ...accum, [ current ]: d[ current ] } ), {} );
} );
componentDidUpdate( prevProps ) {
const { data } = this.props;
const orderedKeys = getOrderedKeys( data );
if ( ! isEqual( [ ...data ].sort(), [ ...prevProps.data ].sort() ) ) {
/* eslint-disable react/no-did-update-set-state */
this.setState( {
orderedKeys: orderedKeys,
visibleData: this.getVisibleData( data, orderedKeys ),
} );
/* eslint-enable react/no-did-update-set-state */
}
}
componentDidMount() {
@ -85,42 +75,15 @@ class WidgetCharts extends Component {
window.removeEventListener( 'resize', this.updateDimensions );
}
getOrderedKeys( data ) {
return [
...new Set(
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 ),
} ) )
.sort( ( a, b ) => b.total - a.total );
}
handleChange( event ) {
const products = this.state.products.map( d => ( {
...d,
selected: d.key === event.target.id ? ! d.selected : d.selected,
} ) );
const someOrders = this.getSomeOrders( products );
const orderedKeys = this.getOrderedKeys( someOrders ).map( d => ( {
...d,
visible: true,
focus: true,
} ) );
this.setState( { products, orderedKeys, someOrders } );
}
handleLegendToggle( event ) {
const { data } = this.props;
const orderedKeys = this.state.orderedKeys.map( d => ( {
...d,
visible: d.key === event.target.id ? ! d.visible : d.visible,
} ) );
this.setState( {
orderedKeys: this.state.orderedKeys.map( d => ( {
...d,
visible: d.key === event.target.id ? ! d.visible : d.visible,
} ) ),
orderedKeys,
visibleData: this.getVisibleData( data, orderedKeys ),
} );
}
@ -138,23 +101,30 @@ class WidgetCharts extends Component {
updateDimensions() {
this.setState( {
bodyWidth: document.getElementById( 'wpbody' ).getBoundingClientRect().width,
width: this.chartRef.current.offsetWidth,
} );
}
getVisibleData( data, orderedKeys ) {
const visibleKeys = orderedKeys.filter( d => d.visible );
return data.map( d => {
const newRow = { date: d.date };
visibleKeys.forEach( row => {
newRow[ row.key ] = d[ row.key ];
} );
return newRow;
} );
}
render() {
const legendDirection =
this.state.orderedKeys.length <= 2 && this.state.bodyWidth > WIDE_BREAKPOINT
? 'row'
: 'column';
const chartDirection =
this.state.orderedKeys.length > 2 && this.state.bodyWidth > WIDE_BREAKPOINT
? 'row'
: 'column';
const { orderedKeys, visibleData, width } = this.state;
const legendDirection = orderedKeys.length <= 2 && width > WIDE_BREAKPOINT ? 'row' : 'column';
const chartDirection = orderedKeys.length > 2 && width > WIDE_BREAKPOINT ? 'row' : 'column';
const legend = (
<Legend
className={ 'woocommerce_dashboard__widget-legend' }
data={ this.state.orderedKeys }
className={ 'woocommerce-chart__legend' }
colorScheme={ d3InterpolateViridis }
data={ orderedKeys }
handleLegendHover={ this.handleLegendHover }
handleLegendToggle={ this.handleLegendToggle }
legendDirection={ legendDirection }
@ -163,57 +133,44 @@ class WidgetCharts extends Component {
const margin = {
bottom: 50,
left: 50,
right: 20,
right: 10,
top: 0,
};
return (
<Fragment>
<Card title={ __( 'Test Categories', 'wc-admin' ) }>
<ul>
{ this.state.products.map( d => (
<li key={ d.key } style={ { display: 'inline', marginRight: '12px' } }>
<label htmlFor={ d.key }>
<input
id={ d.key }
type="checkbox"
onChange={ this.handleChange }
checked={ d.selected }
/>{' '}
{ d.key }
</label>
</li>
) ) }
</ul>
</Card>
<Card title={ __( 'Store Charts', 'wc-admin' ) }>
<div className="woocommerce-dashboard__widget woocommerce-dashboard__widget-chart">
<div className="woocommerce-dashboard__widget-chart-header">
{ this.state.bodyWidth > WIDE_BREAKPOINT && legendDirection === 'row' && legend }
</div>
<div
className={ classNames(
'woocommerce-dashboard__widget-chart-body',
`woocommerce-dashboard__widget-chart-body-${ chartDirection }`
) }
>
{ this.state.bodyWidth > WIDE_BREAKPOINT && legendDirection === 'column' && legend }
<D3Chart
data={ this.state.someOrders }
height={ 300 }
margin={ margin }
orderedKeys={ this.state.orderedKeys }
type={ 'line' }
width={ chartDirection === 'row' ? 722 : 1042 }
/>
</div>
{ this.state.bodyWidth < WIDE_BREAKPOINT && (
<div className="woocommerce-dashboard__widget-chart-footer">{ legend }</div>
) }
</div>
</Card>
</Fragment>
<div className="woocommerce-chart" ref={ this.chartRef }>
<div className="woocommerce-chart__header">
{ width > WIDE_BREAKPOINT && legendDirection === 'row' && legend }
</div>
<div
className={ classNames(
'woocommerce-chart__body',
`woocommerce-chart__body-${ chartDirection }`
) }
>
{ width > WIDE_BREAKPOINT && legendDirection === 'column' && legend }
<D3Chart
colorScheme={ d3InterpolateViridis }
data={ visibleData }
height={ 300 }
margin={ margin }
orderedKeys={ orderedKeys }
type={ 'line' }
width={ chartDirection === 'row' ? width - 320 : width }
/>
</div>
{ width < WIDE_BREAKPOINT && <div className="woocommerce-chart__footer">{ legend }</div> }
</div>
);
}
}
export default WidgetCharts;
Chart.propTypes = {
data: PropTypes.array.isRequired,
title: PropTypes.string,
};
Chart.defaultProps = {
data: [],
};
export default Chart;

View File

@ -5,20 +5,27 @@
import classNames from 'classnames';
import { Component } from '@wordpress/element';
import PropTypes from 'prop-types';
import { scaleOrdinal as d3ScaleOrdinal } from 'd3-scale';
import { interpolateViridis as d3InterpolateViridis } from 'd3-scale-chromatic';
import { range as d3Range } from 'd3-array';
/**
* Internal dependencies
*/
import './style.scss';
import { formatCurrency } from 'lib/currency';
import { getColor } from './utils';
class Legend extends Component {
render() {
const { data, handleLegendHover, handleLegendToggle, legendDirection } = this.props;
const d3Color = d3ScaleOrdinal().range( d3Range( 0, 1.1, 100 / ( data.length - 1 ) / 100 ) );
const {
colorScheme,
data,
handleLegendHover,
handleLegendToggle,
legendDirection,
} = this.props;
const colorParams = {
orderedKeys: data,
colorScheme,
};
return (
<ul
className={ classNames(
@ -27,7 +34,7 @@ class Legend extends Component {
this.props.className
) }
>
{ data.map( ( row, i ) => (
{ data.map( row => (
<li
className="woocommerce-legend__item"
key={ row.key }
@ -44,7 +51,7 @@ class Legend extends Component {
'woocommerce-legend__item-checkmark-checked': row.visible,
} ) }
id={ row.key }
style={ { backgroundColor: d3InterpolateViridis( d3Color( i ) ) } }
style={ { backgroundColor: getColor( row.key, colorParams ) } }
/>
<span className="woocommerce-legend__item-title" id={ row.key }>
{ row.key }
@ -63,6 +70,7 @@ class Legend extends Component {
Legend.propTypes = {
className: PropTypes.string,
colorScheme: PropTypes.func,
data: PropTypes.array.isRequired,
handleLegendToggle: PropTypes.func,
handleLegendHover: PropTypes.func,

View File

@ -1,5 +1,5 @@
/** @format */
.woocommerce-dashboard__widget-chart {
.woocommerce-chart {
display: flex;
flex-direction: column;
justify-content: flex-start;
@ -7,24 +7,24 @@
margin: -20px;
border-top: 1px solid $core-grey-light-200;
.woocommerce-dashboard__widget-chart-header {
.woocommerce-chart__header {
min-height: 50px;
border-top: 1px solid $core-grey-light-200;
}
.woocommerce-dashboard__widget-chart-body {
.woocommerce-chart__body {
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: flex-start;
width: 100%;
&.woocommerce-dashboard__widget-chart-body-column {
&.woocommerce-chart__body-column {
flex-direction: column;
}
}
.woocommerce-dashboard__widget-chart-footer {
.woocommerce-chart__footer {
width: 100%;
.woocommerce-legend {
@ -167,6 +167,7 @@
li {
button {
background-color: $white;
display: inline-flex;
flex-direction: row;
flex-wrap: nowrap;
@ -187,7 +188,6 @@
position: relative;
padding: 3px 0 3px 24px;
cursor: pointer;
font-family: 'SF UI Text';
font-size: 13px;
user-select: none;
width: 100%;

View File

@ -10,7 +10,6 @@
*/
import dummyOrders from './fixtures/dummy';
import {
getColorScale,
getDateSpaces,
getOrderedKeys,
getLineData,
@ -156,16 +155,6 @@ describe( 'getXLineScale', () => {
} );
} );
describe( 'getColorScale', () => {
it( 'properly scale product keys into a range of colors', () => {
const testColorScale = getColorScale( testOrderedKeys );
testOrderedKeys.map( d => testColorScale( d.key ) ); // without this it fails! why? how?
expect( testColorScale( orderedKeys[ 0 ].key ) ).toEqual( 0 );
expect( testColorScale( orderedKeys[ 2 ].key ) ).toEqual( 0.5 );
expect( testColorScale( orderedKeys[ orderedKeys.length - 1 ].key ) ).toEqual( 1 );
} );
} );
describe( 'getYMax', () => {
it( 'calculate the correct maximum y value', () => {
expect( testYMax ).toEqual( 14139347 );

View File

@ -4,18 +4,17 @@
* External dependencies
*/
import { max as d3Max, range as d3Range } from 'd3-array';
import { findIndex } from 'lodash';
import { max as d3Max } from 'd3-array';
import { axisBottom as d3AxisBottom, axisLeft as d3AxisLeft } from 'd3-axis';
import { format as d3Format } from 'd3-format';
import {
scaleBand as d3ScaleBand,
scaleLinear as d3ScaleLinear,
scaleOrdinal as d3ScaleOrdinal,
scaleTime as d3ScaleTime,
} from 'd3-scale';
import { mouse as d3Mouse, select as d3Select } from 'd3-selection';
import { line as d3Line } from 'd3-shape';
import { interpolateViridis as d3InterpolateViridis } from 'd3-scale-chromatic';
import { timeFormat as d3TimeFormat, utcParse as d3UTCParse } from 'd3-time-format';
export const parseDate = d3UTCParse( '%Y-%m-%d' );
@ -87,6 +86,14 @@ export const getUniqueDates = lineData => {
].sort( ( a, b ) => parseDate( a ) - parseDate( b ) );
};
export const getColor = ( key, params ) => {
const keyValue =
params.orderedKeys.length > 1
? findIndex( params.orderedKeys, d => d.key === key ) / ( params.orderedKeys.length - 1 )
: 0;
return params.colorScheme( keyValue );
};
/**
* Describes getXScale
* @param {array} uniqueDates - from `getUniqueDates`
@ -107,7 +114,7 @@ export const getXScale = ( uniqueDates, width ) =>
*/
export const getXGroupScale = ( orderedKeys, xScale ) =>
d3ScaleBand()
.domain( orderedKeys.map( d => d.key ) )
.domain( orderedKeys.filter( d => d.visible ).map( d => d.key ) )
.rangeRound( [ 0, xScale.bandwidth() ] )
.padding( 0.07 );
@ -155,20 +162,11 @@ export const getYTickOffset = ( height, scale, yMax ) =>
/**
* Describes getyTickOffset
* @param {array} orderedKeys - from `getOrderedKeys`
* @returns {function} the D3 ordinal scale of the categories
*/
export const getColorScale = orderedKeys =>
d3ScaleOrdinal().range( d3Range( 0, 1.1, 100 / ( orderedKeys.length - 1 ) / 100 ) );
/**
* Describes getyTickOffset
* @param {array} data - The chart component's `data` prop.
* @param {function} xLineScale - from `getXLineScale`.
* @param {function} yScale - from `getYScale`.
* @returns {function} the D3 line function for plotting all category values
*/
export const getLine = ( data, xLineScale, yScale ) =>
export const getLine = ( xLineScale, yScale ) =>
d3Line()
.x( d => xLineScale( new Date( d.date ) ) )
.y( d => yScale( d.value ) );
@ -201,7 +199,7 @@ export const getDateSpaces = ( uniqueDates, width, xLineScale ) =>
};
} );
export const drawAxis = ( node, data, params ) => {
export const drawAxis = ( node, params ) => {
const xScale = params.type === 'line' ? params.xLineScale : params.xScale;
const yGrids = [];
@ -257,17 +255,16 @@ const showTooltip = ( node, params, d ) => {
let [ xPosition, yPosition ] = d3Mouse( node.node() );
xPosition = xPosition > chartCoords.width - 200 ? xPosition - 200 : xPosition + 20;
yPosition = yPosition > chartCoords.height - 150 ? yPosition - 200 : yPosition + 20;
const keys = params.orderedKeys.map(
( row, i ) => `
<li>
<span class="key-colour" style="background-color:${ d3InterpolateViridis(
params.colorScale( i )
) }"></span>
<span class="key-key">${ row.key }:</span>
<span class="key-value">${ d3Format( ',.0f' )( d[ row.key ] ) }</span>
</li>
`
const keys = params.orderedKeys.filter( row => row.visible ).map(
row => `
<li>
<span class="key-colour" style="background-color:${ getColor( row.key, params ) }"></span>
<span class="key-key">${ row.key }:</span>
<span class="key-value">${ d3Format( ',.0f' )( d[ row.key ] ) }</span>
</li>
`
);
params.tooltip
.style( 'left', xPosition + 'px' )
.style( 'top', yPosition + 'px' )
@ -345,7 +342,7 @@ export const drawLines = ( node, data, params ) => {
.append( 'g' )
.attr( 'class', 'lines' )
.selectAll( '.line-g' )
.data( params.lineData )
.data( params.lineData.filter( d => d.visible ) )
.enter()
.append( 'g' )
.attr( 'class', 'line-g' );
@ -356,7 +353,7 @@ export const drawLines = ( node, data, params ) => {
.attr( 'stroke-width', 3 )
.attr( 'stroke-linejoin', 'round' )
.attr( 'stroke-linecap', 'round' )
.attr( 'stroke', ( d, i ) => d3InterpolateViridis( params.colorScale( i ) ) )
.attr( 'stroke', d => getColor( d.key, params ) )
.style( 'opacity', d => {
const opacity = d.focus ? 1 : 0.1;
return d.visible ? opacity : 0;
@ -365,12 +362,12 @@ export const drawLines = ( node, data, params ) => {
series
.selectAll( 'circle' )
.data( ( d, i ) => d.values.map( row => ( { ...row, i, visible: d.visible } ) ) )
.data( ( d, i ) => d.values.map( row => ( { ...row, i, visible: d.visible, key: d.key } ) ) )
.enter()
.append( 'circle' )
.attr( 'r', 3.5 )
.attr( 'fill', '#fff' )
.attr( 'stroke', d => d3InterpolateViridis( params.colorScale( d.i ) ) )
.attr( 'stroke', d => getColor( d.key, params ) )
.attr( 'stroke-width', 3 )
.style( 'opacity', d => {
const opacity = d.focus ? 1 : 0.1;
@ -403,7 +400,7 @@ export const drawBars = ( node, data, params ) => {
barGroup
.selectAll( '.bar' )
.data( d =>
params.orderedKeys.map( row => ( {
params.orderedKeys.filter( row => row.visible ).map( row => ( {
key: row.key,
focus: row.focus,
value: d[ row.key ],
@ -417,7 +414,7 @@ export const drawBars = ( node, data, params ) => {
.attr( 'y', d => params.yScale( d.value ) )
.attr( 'width', params.xGroupScale.bandwidth() )
.attr( 'height', d => params.height - params.yScale( d.value ) )
.attr( 'fill', ( d, i ) => d3InterpolateViridis( params.colorScale( i ) ) )
.attr( 'fill', d => getColor( d.key, params ) )
.style( 'opacity', d => {
const opacity = d.focus ? 1 : 0.1;
return d.visible ? opacity : 0;

View File

@ -12,7 +12,7 @@ import './style.scss';
import Header from 'layout/header';
import StorePerformance from './store-performance';
import TopSellingProducts from './top-selling-products';
import Chart from 'components/chart';
import Chart from 'components/chart/example';
import Card from 'components/card';
export default class Dashboard extends Component {

View File

@ -20,3 +20,8 @@ $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;
:export {
gaplarge: $gap-large;
gap: $gap;
}

File diff suppressed because it is too large Load Diff