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:
commit
09f33dd1fa
|
@ -3,9 +3,8 @@
|
||||||
/**
|
/**
|
||||||
* External dependencies
|
* External dependencies
|
||||||
*/
|
*/
|
||||||
|
import { isEqual } from 'lodash';
|
||||||
import React from 'react';
|
import { Component, createRef } from '@wordpress/element';
|
||||||
import { Component } from '@wordpress/element';
|
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { format as d3Format } from 'd3-format';
|
import { format as d3Format } from 'd3-format';
|
||||||
|
@ -21,7 +20,6 @@ import {
|
||||||
drawAxis,
|
drawAxis,
|
||||||
drawBars,
|
drawBars,
|
||||||
drawLines,
|
drawLines,
|
||||||
getColorScale,
|
|
||||||
getDateSpaces,
|
getDateSpaces,
|
||||||
getOrderedKeys,
|
getOrderedKeys,
|
||||||
getLine,
|
getLine,
|
||||||
|
@ -37,52 +35,33 @@ import {
|
||||||
} from './utils';
|
} from './utils';
|
||||||
|
|
||||||
class D3Chart extends Component {
|
class D3Chart extends Component {
|
||||||
static propTypes = {
|
constructor( props ) {
|
||||||
className: PropTypes.string,
|
super( props );
|
||||||
data: PropTypes.array.isRequired,
|
this.getAllData = this.getAllData.bind( this );
|
||||||
height: PropTypes.number,
|
this.state = {
|
||||||
legend: PropTypes.array,
|
allData: this.getAllData( props ),
|
||||||
margin: PropTypes.shape( {
|
width: props.width,
|
||||||
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,
|
|
||||||
};
|
};
|
||||||
|
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 };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
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 */
|
||||||
|
}
|
||||||
|
|
||||||
|
getAllData( props ) {
|
||||||
|
const orderedKeys =
|
||||||
|
props.orderedKeys || getOrderedKeys( props.data, getUniqueKeys( props.data ) );
|
||||||
|
return [ ...props.data, ...orderedKeys ];
|
||||||
}
|
}
|
||||||
|
|
||||||
drawChart = ( node, params ) => {
|
drawChart = ( node, params ) => {
|
||||||
|
@ -97,8 +76,7 @@ class D3Chart extends Component {
|
||||||
width: params.width - margin.left - margin.right,
|
width: params.width - margin.left - margin.right,
|
||||||
tooltip: d3Select( this.tooltipRef.current ),
|
tooltip: d3Select( this.tooltipRef.current ),
|
||||||
} );
|
} );
|
||||||
|
drawAxis( g, adjParams );
|
||||||
drawAxis( g, data, adjParams );
|
|
||||||
type === 'line' && drawLines( g, data, adjParams );
|
type === 'line' && drawLines( g, data, adjParams );
|
||||||
type === 'bar' && drawBars( g, data, adjParams );
|
type === 'bar' && drawBars( g, data, adjParams );
|
||||||
|
|
||||||
|
@ -106,25 +84,26 @@ class D3Chart extends Component {
|
||||||
};
|
};
|
||||||
|
|
||||||
getParams = node => {
|
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 calculatedWidth = width || node.offsetWidth;
|
||||||
const calculatedHeight = height || node.offsetHeight;
|
const calculatedHeight = height || node.offsetHeight;
|
||||||
const scale = width / node.offsetWidth;
|
const scale = width / node.offsetWidth;
|
||||||
const adjHeight = calculatedHeight - margin.top - margin.bottom;
|
const adjHeight = calculatedHeight - margin.top - margin.bottom;
|
||||||
const adjWidth = calculatedWidth - margin.left - margin.right;
|
const adjWidth = calculatedWidth - margin.left - margin.right;
|
||||||
const uniqueKeys = getUniqueKeys( data );
|
const uniqueKeys = getUniqueKeys( data );
|
||||||
const newOrderedKeys = orderedKeys ? orderedKeys : getOrderedKeys( data, uniqueKeys );
|
const newOrderedKeys = orderedKeys || getOrderedKeys( data, uniqueKeys );
|
||||||
const lineData = getLineData( data, orderedKeys );
|
const lineData = getLineData( data, newOrderedKeys );
|
||||||
const yMax = getYMax( lineData );
|
const yMax = getYMax( lineData );
|
||||||
const yScale = getYScale( adjHeight, yMax );
|
const yScale = getYScale( adjHeight, yMax );
|
||||||
const uniqueDates = getUniqueDates( lineData );
|
const uniqueDates = getUniqueDates( lineData );
|
||||||
const xLineScale = getXLineScale( uniqueDates, adjWidth );
|
const xLineScale = getXLineScale( uniqueDates, adjWidth );
|
||||||
const xScale = getXScale( uniqueDates, adjWidth );
|
const xScale = getXScale( uniqueDates, adjWidth );
|
||||||
return {
|
return {
|
||||||
colorScale: getColorScale( orderedKeys ),
|
colorScheme,
|
||||||
dateSpaces: getDateSpaces( uniqueDates, adjWidth, xLineScale ),
|
dateSpaces: getDateSpaces( uniqueDates, adjWidth, xLineScale ),
|
||||||
height: calculatedHeight,
|
height: calculatedHeight,
|
||||||
line: getLine( data, xLineScale, yScale ),
|
line: getLine( xLineScale, yScale ),
|
||||||
lineData,
|
lineData,
|
||||||
margin,
|
margin,
|
||||||
orderedKeys: newOrderedKeys,
|
orderedKeys: newOrderedKeys,
|
||||||
|
@ -148,14 +127,17 @@ class D3Chart extends Component {
|
||||||
if ( ! this.props.data ) {
|
if ( ! this.props.data ) {
|
||||||
return null; // TODO: improve messaging
|
return null; // TODO: improve messaging
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
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
|
<D3Base
|
||||||
className={ classNames( this.props.className ) }
|
className={ classNames( this.props.className ) }
|
||||||
data={ this.state.allData }
|
data={ this.state.allData }
|
||||||
drawChart={ this.drawChart }
|
drawChart={ this.drawChart }
|
||||||
getParams={ this.getParams }
|
getParams={ this.getParams }
|
||||||
|
width={ this.state.width }
|
||||||
/>
|
/>
|
||||||
<div className="tooltip" ref={ this.tooltipRef } />
|
<div className="tooltip" ref={ this.tooltipRef } />
|
||||||
</div>
|
</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;
|
export default D3Chart;
|
||||||
|
|
|
@ -5,8 +5,8 @@
|
||||||
*/
|
*/
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import { Component, createRef } from '@wordpress/element';
|
||||||
import { isEmpty } from 'lodash';
|
import { isEmpty, isEqual } from 'lodash';
|
||||||
import { select as d3Select } from 'd3-selection';
|
import { select as d3Select } from 'd3-selection';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -38,25 +38,30 @@ export default class D3Base extends Component {
|
||||||
params: null,
|
params: null,
|
||||||
drawChart: null,
|
drawChart: null,
|
||||||
getParams: null,
|
getParams: null,
|
||||||
|
width: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
chartRef = React.createRef();
|
chartRef = createRef();
|
||||||
|
|
||||||
static getDerivedStateFromProps( nextProps, prevState ) {
|
static getDerivedStateFromProps( nextProps, prevState ) {
|
||||||
let state = {};
|
let state = {};
|
||||||
|
|
||||||
if ( nextProps.data !== prevState.data ) {
|
if ( ! isEqual( nextProps.data, prevState.data ) ) {
|
||||||
state = { ...state, data: nextProps.data };
|
state = { ...state, data: nextProps.data };
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( nextProps.drawChart !== prevState.drawChart ) {
|
if ( ! isEqual( nextProps.drawChart, prevState.drawChart ) ) {
|
||||||
state = { ...state, drawChart: nextProps.drawChart };
|
state = { ...state, drawChart: nextProps.drawChart };
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( nextProps.getParams !== prevState.getParams ) {
|
if ( ! isEqual( nextProps.getParams, prevState.getParams ) ) {
|
||||||
state = { ...state, getParams: nextProps.getParams };
|
state = { ...state, getParams: nextProps.getParams };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ( nextProps.width !== prevState.width ) {
|
||||||
|
state = { ...state, width: nextProps.width };
|
||||||
|
}
|
||||||
|
|
||||||
if ( ! isEmpty( state ) ) {
|
if ( ! isEmpty( state ) ) {
|
||||||
return { ...state, params: null };
|
return { ...state, params: null };
|
||||||
}
|
}
|
||||||
|
@ -72,8 +77,9 @@ export default class D3Base extends Component {
|
||||||
|
|
||||||
shouldComponentUpdate( nextProps, nextState ) {
|
shouldComponentUpdate( nextProps, nextState ) {
|
||||||
return (
|
return (
|
||||||
( nextState.params !== null && this.state.params !== nextState.params ) ||
|
( nextState.params !== null && ! isEqual( this.state.params, nextState.params ) ) ||
|
||||||
this.state.data !== nextState.data
|
! isEqual( this.state.data, nextState.data ) ||
|
||||||
|
this.state.width !== nextState.width
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
|
@ -2,90 +2,22 @@
|
||||||
/**
|
/**
|
||||||
* External dependencies
|
* External dependencies
|
||||||
*/
|
*/
|
||||||
import { __ } from '@wordpress/i18n';
|
|
||||||
import classNames from 'classnames';
|
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
|
* Internal dependencies
|
||||||
*/
|
*/
|
||||||
import Card from 'components/card';
|
|
||||||
import D3Chart from './charts';
|
import D3Chart from './charts';
|
||||||
import dummyOrders from './test/fixtures/dummy';
|
|
||||||
import Legend from './legend';
|
import Legend from './legend';
|
||||||
|
import { gap, gaplarge } from 'stylesheets/abstracts/_variables.scss';
|
||||||
|
|
||||||
const WIDE_BREAKPOINT = 1130;
|
const WIDE_BREAKPOINT = 1100;
|
||||||
|
|
||||||
class WidgetCharts extends Component {
|
function getOrderedKeys( data ) {
|
||||||
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 );
|
|
||||||
this.state = {
|
|
||||||
products,
|
|
||||||
orderedKeys: this.getOrderedKeys( someOrders ).map( d => ( {
|
|
||||||
...d,
|
|
||||||
visible: true,
|
|
||||||
focus: true,
|
|
||||||
} ) ),
|
|
||||||
someOrders,
|
|
||||||
bodyWidth: document.getElementById( 'wpbody' ).getBoundingClientRect().width,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
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 ] } ), {} );
|
|
||||||
} );
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
window.addEventListener( 'resize', this.updateDimensions );
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillUnmount() {
|
|
||||||
window.removeEventListener( 'resize', this.updateDimensions );
|
|
||||||
}
|
|
||||||
|
|
||||||
getOrderedKeys( data ) {
|
|
||||||
return [
|
return [
|
||||||
...new Set(
|
...new Set(
|
||||||
data.reduce( ( accum, curr ) => {
|
data.reduce( ( accum, curr ) => {
|
||||||
|
@ -97,30 +29,61 @@ class WidgetCharts extends Component {
|
||||||
.map( key => ( {
|
.map( key => ( {
|
||||||
key,
|
key,
|
||||||
total: data.reduce( ( a, c ) => a + c[ key ], 0 ),
|
total: data.reduce( ( a, c ) => a + c[ key ], 0 ),
|
||||||
|
visible: true,
|
||||||
|
focus: true,
|
||||||
} ) )
|
} ) )
|
||||||
.sort( ( a, b ) => b.total - a.total );
|
.sort( ( a, b ) => b.total - a.total );
|
||||||
}
|
}
|
||||||
|
|
||||||
handleChange( event ) {
|
class Chart extends Component {
|
||||||
const products = this.state.products.map( d => ( {
|
constructor( props ) {
|
||||||
...d,
|
super( props );
|
||||||
selected: d.key === event.target.id ? ! d.selected : d.selected,
|
this.chartRef = createRef();
|
||||||
} ) );
|
const wpBody = document.getElementById( 'wpbody' ).getBoundingClientRect().width;
|
||||||
const someOrders = this.getSomeOrders( products );
|
const wpWrap = document.getElementById( 'wpwrap' ).getBoundingClientRect().width;
|
||||||
const orderedKeys = this.getOrderedKeys( someOrders ).map( d => ( {
|
const calcGap = wpWrap > 782 ? gaplarge.match( /\d+/ )[ 0 ] : gap.match( /\d+/ )[ 0 ];
|
||||||
...d,
|
this.state = {
|
||||||
visible: true,
|
data: props.data,
|
||||||
focus: true,
|
orderedKeys: getOrderedKeys( props.data ),
|
||||||
} ) );
|
visibleData: [ ...props.data ],
|
||||||
this.setState( { products, orderedKeys, someOrders } );
|
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 );
|
||||||
|
}
|
||||||
|
|
||||||
|
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() {
|
||||||
|
window.addEventListener( 'resize', this.updateDimensions );
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
window.removeEventListener( 'resize', this.updateDimensions );
|
||||||
}
|
}
|
||||||
|
|
||||||
handleLegendToggle( event ) {
|
handleLegendToggle( event ) {
|
||||||
this.setState( {
|
const { data } = this.props;
|
||||||
orderedKeys: this.state.orderedKeys.map( d => ( {
|
const orderedKeys = this.state.orderedKeys.map( d => ( {
|
||||||
...d,
|
...d,
|
||||||
visible: d.key === event.target.id ? ! d.visible : d.visible,
|
visible: d.key === event.target.id ? ! d.visible : d.visible,
|
||||||
} ) ),
|
} ) );
|
||||||
|
this.setState( {
|
||||||
|
orderedKeys,
|
||||||
|
visibleData: this.getVisibleData( data, orderedKeys ),
|
||||||
} );
|
} );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -138,23 +101,30 @@ class WidgetCharts extends Component {
|
||||||
|
|
||||||
updateDimensions() {
|
updateDimensions() {
|
||||||
this.setState( {
|
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() {
|
render() {
|
||||||
const legendDirection =
|
const { orderedKeys, visibleData, width } = this.state;
|
||||||
this.state.orderedKeys.length <= 2 && this.state.bodyWidth > WIDE_BREAKPOINT
|
const legendDirection = orderedKeys.length <= 2 && width > WIDE_BREAKPOINT ? 'row' : 'column';
|
||||||
? 'row'
|
const chartDirection = orderedKeys.length > 2 && width > WIDE_BREAKPOINT ? 'row' : 'column';
|
||||||
: 'column';
|
|
||||||
const chartDirection =
|
|
||||||
this.state.orderedKeys.length > 2 && this.state.bodyWidth > WIDE_BREAKPOINT
|
|
||||||
? 'row'
|
|
||||||
: 'column';
|
|
||||||
const legend = (
|
const legend = (
|
||||||
<Legend
|
<Legend
|
||||||
className={ 'woocommerce_dashboard__widget-legend' }
|
className={ 'woocommerce-chart__legend' }
|
||||||
data={ this.state.orderedKeys }
|
colorScheme={ d3InterpolateViridis }
|
||||||
|
data={ orderedKeys }
|
||||||
handleLegendHover={ this.handleLegendHover }
|
handleLegendHover={ this.handleLegendHover }
|
||||||
handleLegendToggle={ this.handleLegendToggle }
|
handleLegendToggle={ this.handleLegendToggle }
|
||||||
legendDirection={ legendDirection }
|
legendDirection={ legendDirection }
|
||||||
|
@ -163,57 +133,44 @@ class WidgetCharts extends Component {
|
||||||
const margin = {
|
const margin = {
|
||||||
bottom: 50,
|
bottom: 50,
|
||||||
left: 50,
|
left: 50,
|
||||||
right: 20,
|
right: 10,
|
||||||
top: 0,
|
top: 0,
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<div className="woocommerce-chart" ref={ this.chartRef }>
|
||||||
<Card title={ __( 'Test Categories', 'wc-admin' ) }>
|
<div className="woocommerce-chart__header">
|
||||||
<ul>
|
{ width > WIDE_BREAKPOINT && legendDirection === 'row' && legend }
|
||||||
{ 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>
|
||||||
<div
|
<div
|
||||||
className={ classNames(
|
className={ classNames(
|
||||||
'woocommerce-dashboard__widget-chart-body',
|
'woocommerce-chart__body',
|
||||||
`woocommerce-dashboard__widget-chart-body-${ chartDirection }`
|
`woocommerce-chart__body-${ chartDirection }`
|
||||||
) }
|
) }
|
||||||
>
|
>
|
||||||
{ this.state.bodyWidth > WIDE_BREAKPOINT && legendDirection === 'column' && legend }
|
{ width > WIDE_BREAKPOINT && legendDirection === 'column' && legend }
|
||||||
<D3Chart
|
<D3Chart
|
||||||
data={ this.state.someOrders }
|
colorScheme={ d3InterpolateViridis }
|
||||||
|
data={ visibleData }
|
||||||
height={ 300 }
|
height={ 300 }
|
||||||
margin={ margin }
|
margin={ margin }
|
||||||
orderedKeys={ this.state.orderedKeys }
|
orderedKeys={ orderedKeys }
|
||||||
type={ 'line' }
|
type={ 'line' }
|
||||||
width={ chartDirection === 'row' ? 722 : 1042 }
|
width={ chartDirection === 'row' ? width - 320 : width }
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{ this.state.bodyWidth < WIDE_BREAKPOINT && (
|
{ width < WIDE_BREAKPOINT && <div className="woocommerce-chart__footer">{ legend }</div> }
|
||||||
<div className="woocommerce-dashboard__widget-chart-footer">{ legend }</div>
|
|
||||||
) }
|
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
|
||||||
</Fragment>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default WidgetCharts;
|
Chart.propTypes = {
|
||||||
|
data: PropTypes.array.isRequired,
|
||||||
|
title: PropTypes.string,
|
||||||
|
};
|
||||||
|
|
||||||
|
Chart.defaultProps = {
|
||||||
|
data: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Chart;
|
||||||
|
|
|
@ -5,20 +5,27 @@
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { Component } from '@wordpress/element';
|
import { Component } from '@wordpress/element';
|
||||||
import PropTypes from 'prop-types';
|
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
|
* Internal dependencies
|
||||||
*/
|
*/
|
||||||
import './style.scss';
|
import './style.scss';
|
||||||
import { formatCurrency } from 'lib/currency';
|
import { formatCurrency } from 'lib/currency';
|
||||||
|
import { getColor } from './utils';
|
||||||
|
|
||||||
class Legend extends Component {
|
class Legend extends Component {
|
||||||
render() {
|
render() {
|
||||||
const { data, handleLegendHover, handleLegendToggle, legendDirection } = this.props;
|
const {
|
||||||
const d3Color = d3ScaleOrdinal().range( d3Range( 0, 1.1, 100 / ( data.length - 1 ) / 100 ) );
|
colorScheme,
|
||||||
|
data,
|
||||||
|
handleLegendHover,
|
||||||
|
handleLegendToggle,
|
||||||
|
legendDirection,
|
||||||
|
} = this.props;
|
||||||
|
const colorParams = {
|
||||||
|
orderedKeys: data,
|
||||||
|
colorScheme,
|
||||||
|
};
|
||||||
return (
|
return (
|
||||||
<ul
|
<ul
|
||||||
className={ classNames(
|
className={ classNames(
|
||||||
|
@ -27,7 +34,7 @@ class Legend extends Component {
|
||||||
this.props.className
|
this.props.className
|
||||||
) }
|
) }
|
||||||
>
|
>
|
||||||
{ data.map( ( row, i ) => (
|
{ data.map( row => (
|
||||||
<li
|
<li
|
||||||
className="woocommerce-legend__item"
|
className="woocommerce-legend__item"
|
||||||
key={ row.key }
|
key={ row.key }
|
||||||
|
@ -44,7 +51,7 @@ class Legend extends Component {
|
||||||
'woocommerce-legend__item-checkmark-checked': row.visible,
|
'woocommerce-legend__item-checkmark-checked': row.visible,
|
||||||
} ) }
|
} ) }
|
||||||
id={ row.key }
|
id={ row.key }
|
||||||
style={ { backgroundColor: d3InterpolateViridis( d3Color( i ) ) } }
|
style={ { backgroundColor: getColor( row.key, colorParams ) } }
|
||||||
/>
|
/>
|
||||||
<span className="woocommerce-legend__item-title" id={ row.key }>
|
<span className="woocommerce-legend__item-title" id={ row.key }>
|
||||||
{ row.key }
|
{ row.key }
|
||||||
|
@ -63,6 +70,7 @@ class Legend extends Component {
|
||||||
|
|
||||||
Legend.propTypes = {
|
Legend.propTypes = {
|
||||||
className: PropTypes.string,
|
className: PropTypes.string,
|
||||||
|
colorScheme: PropTypes.func,
|
||||||
data: PropTypes.array.isRequired,
|
data: PropTypes.array.isRequired,
|
||||||
handleLegendToggle: PropTypes.func,
|
handleLegendToggle: PropTypes.func,
|
||||||
handleLegendHover: PropTypes.func,
|
handleLegendHover: PropTypes.func,
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/** @format */
|
/** @format */
|
||||||
.woocommerce-dashboard__widget-chart {
|
.woocommerce-chart {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
|
@ -7,24 +7,24 @@
|
||||||
margin: -20px;
|
margin: -20px;
|
||||||
border-top: 1px solid $core-grey-light-200;
|
border-top: 1px solid $core-grey-light-200;
|
||||||
|
|
||||||
.woocommerce-dashboard__widget-chart-header {
|
.woocommerce-chart__header {
|
||||||
min-height: 50px;
|
min-height: 50px;
|
||||||
border-top: 1px solid $core-grey-light-200;
|
border-top: 1px solid $core-grey-light-200;
|
||||||
}
|
}
|
||||||
|
|
||||||
.woocommerce-dashboard__widget-chart-body {
|
.woocommerce-chart__body {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
&.woocommerce-dashboard__widget-chart-body-column {
|
&.woocommerce-chart__body-column {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.woocommerce-dashboard__widget-chart-footer {
|
.woocommerce-chart__footer {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
.woocommerce-legend {
|
.woocommerce-legend {
|
||||||
|
@ -167,6 +167,7 @@
|
||||||
|
|
||||||
li {
|
li {
|
||||||
button {
|
button {
|
||||||
|
background-color: $white;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
flex-wrap: nowrap;
|
flex-wrap: nowrap;
|
||||||
|
@ -187,7 +188,6 @@
|
||||||
position: relative;
|
position: relative;
|
||||||
padding: 3px 0 3px 24px;
|
padding: 3px 0 3px 24px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-family: 'SF UI Text';
|
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
|
@ -10,7 +10,6 @@
|
||||||
*/
|
*/
|
||||||
import dummyOrders from './fixtures/dummy';
|
import dummyOrders from './fixtures/dummy';
|
||||||
import {
|
import {
|
||||||
getColorScale,
|
|
||||||
getDateSpaces,
|
getDateSpaces,
|
||||||
getOrderedKeys,
|
getOrderedKeys,
|
||||||
getLineData,
|
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', () => {
|
describe( 'getYMax', () => {
|
||||||
it( 'calculate the correct maximum y value', () => {
|
it( 'calculate the correct maximum y value', () => {
|
||||||
expect( testYMax ).toEqual( 14139347 );
|
expect( testYMax ).toEqual( 14139347 );
|
||||||
|
|
|
@ -4,18 +4,17 @@
|
||||||
* External dependencies
|
* 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 { axisBottom as d3AxisBottom, axisLeft as d3AxisLeft } from 'd3-axis';
|
||||||
import { format as d3Format } from 'd3-format';
|
import { format as d3Format } from 'd3-format';
|
||||||
import {
|
import {
|
||||||
scaleBand as d3ScaleBand,
|
scaleBand as d3ScaleBand,
|
||||||
scaleLinear as d3ScaleLinear,
|
scaleLinear as d3ScaleLinear,
|
||||||
scaleOrdinal as d3ScaleOrdinal,
|
|
||||||
scaleTime as d3ScaleTime,
|
scaleTime as d3ScaleTime,
|
||||||
} from 'd3-scale';
|
} from 'd3-scale';
|
||||||
import { mouse as d3Mouse, select as d3Select } from 'd3-selection';
|
import { mouse as d3Mouse, select as d3Select } from 'd3-selection';
|
||||||
import { line as d3Line } from 'd3-shape';
|
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';
|
import { timeFormat as d3TimeFormat, utcParse as d3UTCParse } from 'd3-time-format';
|
||||||
|
|
||||||
export const parseDate = d3UTCParse( '%Y-%m-%d' );
|
export const parseDate = d3UTCParse( '%Y-%m-%d' );
|
||||||
|
@ -87,6 +86,14 @@ export const getUniqueDates = lineData => {
|
||||||
].sort( ( a, b ) => parseDate( a ) - parseDate( b ) );
|
].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
|
* Describes getXScale
|
||||||
* @param {array} uniqueDates - from `getUniqueDates`
|
* @param {array} uniqueDates - from `getUniqueDates`
|
||||||
|
@ -107,7 +114,7 @@ export const getXScale = ( uniqueDates, width ) =>
|
||||||
*/
|
*/
|
||||||
export const getXGroupScale = ( orderedKeys, xScale ) =>
|
export const getXGroupScale = ( orderedKeys, xScale ) =>
|
||||||
d3ScaleBand()
|
d3ScaleBand()
|
||||||
.domain( orderedKeys.map( d => d.key ) )
|
.domain( orderedKeys.filter( d => d.visible ).map( d => d.key ) )
|
||||||
.rangeRound( [ 0, xScale.bandwidth() ] )
|
.rangeRound( [ 0, xScale.bandwidth() ] )
|
||||||
.padding( 0.07 );
|
.padding( 0.07 );
|
||||||
|
|
||||||
|
@ -155,20 +162,11 @@ export const getYTickOffset = ( height, scale, yMax ) =>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Describes getyTickOffset
|
* 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} xLineScale - from `getXLineScale`.
|
||||||
* @param {function} yScale - from `getYScale`.
|
* @param {function} yScale - from `getYScale`.
|
||||||
* @returns {function} the D3 line function for plotting all category values
|
* @returns {function} the D3 line function for plotting all category values
|
||||||
*/
|
*/
|
||||||
export const getLine = ( data, xLineScale, yScale ) =>
|
export const getLine = ( xLineScale, yScale ) =>
|
||||||
d3Line()
|
d3Line()
|
||||||
.x( d => xLineScale( new Date( d.date ) ) )
|
.x( d => xLineScale( new Date( d.date ) ) )
|
||||||
.y( d => yScale( d.value ) );
|
.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 xScale = params.type === 'line' ? params.xLineScale : params.xScale;
|
||||||
|
|
||||||
const yGrids = [];
|
const yGrids = [];
|
||||||
|
@ -257,17 +255,16 @@ const showTooltip = ( node, params, d ) => {
|
||||||
let [ xPosition, yPosition ] = d3Mouse( node.node() );
|
let [ xPosition, yPosition ] = d3Mouse( node.node() );
|
||||||
xPosition = xPosition > chartCoords.width - 200 ? xPosition - 200 : xPosition + 20;
|
xPosition = xPosition > chartCoords.width - 200 ? xPosition - 200 : xPosition + 20;
|
||||||
yPosition = yPosition > chartCoords.height - 150 ? yPosition - 200 : yPosition + 20;
|
yPosition = yPosition > chartCoords.height - 150 ? yPosition - 200 : yPosition + 20;
|
||||||
const keys = params.orderedKeys.map(
|
const keys = params.orderedKeys.filter( row => row.visible ).map(
|
||||||
( row, i ) => `
|
row => `
|
||||||
<li>
|
<li>
|
||||||
<span class="key-colour" style="background-color:${ d3InterpolateViridis(
|
<span class="key-colour" style="background-color:${ getColor( row.key, params ) }"></span>
|
||||||
params.colorScale( i )
|
|
||||||
) }"></span>
|
|
||||||
<span class="key-key">${ row.key }:</span>
|
<span class="key-key">${ row.key }:</span>
|
||||||
<span class="key-value">${ d3Format( ',.0f' )( d[ row.key ] ) }</span>
|
<span class="key-value">${ d3Format( ',.0f' )( d[ row.key ] ) }</span>
|
||||||
</li>
|
</li>
|
||||||
`
|
`
|
||||||
);
|
);
|
||||||
|
|
||||||
params.tooltip
|
params.tooltip
|
||||||
.style( 'left', xPosition + 'px' )
|
.style( 'left', xPosition + 'px' )
|
||||||
.style( 'top', yPosition + 'px' )
|
.style( 'top', yPosition + 'px' )
|
||||||
|
@ -345,7 +342,7 @@ export const drawLines = ( node, data, params ) => {
|
||||||
.append( 'g' )
|
.append( 'g' )
|
||||||
.attr( 'class', 'lines' )
|
.attr( 'class', 'lines' )
|
||||||
.selectAll( '.line-g' )
|
.selectAll( '.line-g' )
|
||||||
.data( params.lineData )
|
.data( params.lineData.filter( d => d.visible ) )
|
||||||
.enter()
|
.enter()
|
||||||
.append( 'g' )
|
.append( 'g' )
|
||||||
.attr( 'class', 'line-g' );
|
.attr( 'class', 'line-g' );
|
||||||
|
@ -356,7 +353,7 @@ export const drawLines = ( node, data, params ) => {
|
||||||
.attr( 'stroke-width', 3 )
|
.attr( 'stroke-width', 3 )
|
||||||
.attr( 'stroke-linejoin', 'round' )
|
.attr( 'stroke-linejoin', 'round' )
|
||||||
.attr( 'stroke-linecap', 'round' )
|
.attr( 'stroke-linecap', 'round' )
|
||||||
.attr( 'stroke', ( d, i ) => d3InterpolateViridis( params.colorScale( i ) ) )
|
.attr( 'stroke', d => getColor( d.key, params ) )
|
||||||
.style( 'opacity', d => {
|
.style( 'opacity', d => {
|
||||||
const opacity = d.focus ? 1 : 0.1;
|
const opacity = d.focus ? 1 : 0.1;
|
||||||
return d.visible ? opacity : 0;
|
return d.visible ? opacity : 0;
|
||||||
|
@ -365,12 +362,12 @@ export const drawLines = ( node, data, params ) => {
|
||||||
|
|
||||||
series
|
series
|
||||||
.selectAll( 'circle' )
|
.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()
|
.enter()
|
||||||
.append( 'circle' )
|
.append( 'circle' )
|
||||||
.attr( 'r', 3.5 )
|
.attr( 'r', 3.5 )
|
||||||
.attr( 'fill', '#fff' )
|
.attr( 'fill', '#fff' )
|
||||||
.attr( 'stroke', d => d3InterpolateViridis( params.colorScale( d.i ) ) )
|
.attr( 'stroke', d => getColor( d.key, params ) )
|
||||||
.attr( 'stroke-width', 3 )
|
.attr( 'stroke-width', 3 )
|
||||||
.style( 'opacity', d => {
|
.style( 'opacity', d => {
|
||||||
const opacity = d.focus ? 1 : 0.1;
|
const opacity = d.focus ? 1 : 0.1;
|
||||||
|
@ -403,7 +400,7 @@ export const drawBars = ( node, data, params ) => {
|
||||||
barGroup
|
barGroup
|
||||||
.selectAll( '.bar' )
|
.selectAll( '.bar' )
|
||||||
.data( d =>
|
.data( d =>
|
||||||
params.orderedKeys.map( row => ( {
|
params.orderedKeys.filter( row => row.visible ).map( row => ( {
|
||||||
key: row.key,
|
key: row.key,
|
||||||
focus: row.focus,
|
focus: row.focus,
|
||||||
value: d[ row.key ],
|
value: d[ row.key ],
|
||||||
|
@ -417,7 +414,7 @@ export const drawBars = ( node, data, params ) => {
|
||||||
.attr( 'y', d => params.yScale( d.value ) )
|
.attr( 'y', d => params.yScale( d.value ) )
|
||||||
.attr( 'width', params.xGroupScale.bandwidth() )
|
.attr( 'width', params.xGroupScale.bandwidth() )
|
||||||
.attr( 'height', d => params.height - params.yScale( d.value ) )
|
.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 => {
|
.style( 'opacity', d => {
|
||||||
const opacity = d.focus ? 1 : 0.1;
|
const opacity = d.focus ? 1 : 0.1;
|
||||||
return d.visible ? opacity : 0;
|
return d.visible ? opacity : 0;
|
||||||
|
|
|
@ -12,7 +12,7 @@ import './style.scss';
|
||||||
import Header from 'layout/header';
|
import Header from 'layout/header';
|
||||||
import StorePerformance from './store-performance';
|
import StorePerformance from './store-performance';
|
||||||
import TopSellingProducts from './top-selling-products';
|
import TopSellingProducts from './top-selling-products';
|
||||||
import Chart from 'components/chart';
|
import Chart from 'components/chart/example';
|
||||||
import Card from 'components/card';
|
import Card from 'components/card';
|
||||||
|
|
||||||
export default class Dashboard extends Component {
|
export default class Dashboard extends Component {
|
||||||
|
|
|
@ -20,3 +20,8 @@ $light-gray-500: $core-grey-light-500;
|
||||||
$dark-gray-300: $core-grey-dark-300;
|
$dark-gray-300: $core-grey-dark-300;
|
||||||
$dark-gray-900: $core-grey-dark-900;
|
$dark-gray-900: $core-grey-dark-900;
|
||||||
$alert-red: $error-red;
|
$alert-red: $error-red;
|
||||||
|
|
||||||
|
:export {
|
||||||
|
gaplarge: $gap-large;
|
||||||
|
gap: $gap;
|
||||||
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue