/** @format */ /** * External dependencies */ import { Component, createRef } from '@wordpress/element'; import PropTypes from 'prop-types'; import classNames from 'classnames'; import { findIndex, isEqual } from 'lodash'; import { format as d3Format } from 'd3-format'; import { timeFormat as d3TimeFormat } from 'd3-time-format'; import { select as d3Select } from 'd3-selection'; import { range as d3Range } from 'd3-array'; import { scaleOrdinal as d3ScaleOrdinal } from 'd3-scale'; /** * Internal dependencies */ import './style.scss'; import D3Base from './d3-base'; import { drawAxis, drawBars, drawLines, getDateSpaces, getOrderedKeys, getLine, getLineData, getUniqueKeys, getUniqueDates, getXScale, getXGroupScale, getXLineScale, getYMax, getYScale, getYTickOffset, } from './utils'; class D3Chart extends Component { constructor( props ) { super( props ); this.getAllData = this.getAllData.bind( this ); this.state = { allData: this.getAllData( props ), width: props.width, }; this.tooltipRef = createRef(); } 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 ) => { const { margin, type } = this.props; const { data } = this.state; const g = node .attr( 'id', 'chart' ) .append( 'g' ) .attr( 'transform', `translate(${ margin.left },${ margin.top })` ); const adjParams = Object.assign( {}, params, { height: params.height - margin.top - margin.bottom, width: params.width - margin.left - margin.right, tooltip: d3Select( this.tooltipRef.current ), } ); drawAxis( g, adjParams ); type === 'line' && drawLines( g, data, adjParams ); type === 'bar' && drawBars( g, data, adjParams ); return node; }; getParams = node => { const { 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 || 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 ); const colorScale = d3ScaleOrdinal().range( d3Range( 0, 1.1, 100 / ( newOrderedKeys.length - 1 ) / 100 ) ); return { colorScale: key => colorScale( findIndex( orderedKeys, d => d.key === key ) ), dateSpaces: getDateSpaces( uniqueDates, adjWidth, xLineScale ), height: calculatedHeight, line: getLine( data, xLineScale, yScale ), lineData, margin, orderedKeys: newOrderedKeys, scale, type, uniqueDates, uniqueKeys, width: calculatedWidth, xFormat: d3TimeFormat( xFormat ), xGroupScale: getXGroupScale( orderedKeys, xScale ), xLineScale, xScale, yMax, yScale, yTickOffset: getYTickOffset( adjHeight, scale, yMax ), yFormat: d3Format( yFormat ), }; }; render() { if ( ! this.props.data ) { return null; // TODO: improve messaging } return (