/** * @fileOverview Cartesian Grid */ import React, { PureComponent, ReactElement, SVGProps } from 'react'; import _ from 'lodash'; import { isNumber } from '../util/DataUtils'; import { ChartOffset, D3Scale, filterProps } from '../util/types'; import { Props as XAxisProps } from './XAxis'; import { Props as YAxisProps } from './YAxis'; type GridLineType = | SVGProps | ReactElement | ((props: any) => ReactElement) | boolean; interface IntrnalCartesianGridProps { x?: number; y?: number; width?: number; height?: number; horizontalCoordinatesGenerator?: (props: any) => number[]; verticalCoordinatesGenerator?: (props: any) => number[]; xAxis?: Omit & { scale: D3Scale }; yAxis?: Omit & { scale: D3Scale }; offset?: ChartOffset; chartWidth?: number; chartHeight?: number; } interface CartesianGridProps extends IntrnalCartesianGridProps { horizontal?: GridLineType; vertical?: GridLineType; horizontalPoints?: number[]; verticalPoints?: number[]; verticalFill?: string[]; horizontalFill?: string[]; } export type Props = SVGProps & CartesianGridProps; export class CartesianGrid extends PureComponent { static displayName = 'CartesianGrid'; static defaultProps = { horizontal: true, vertical: true, // The ordinates of horizontal grid lines horizontalPoints: [] as CartesianGridProps['horizontalPoints'], // The abscissas of vertical grid lines verticalPoints: [] as CartesianGridProps['verticalPoints'], stroke: '#ccc', fill: 'none', // The fill of colors of grid lines verticalFill: [] as CartesianGridProps['verticalFill'], horizontalFill: [] as CartesianGridProps['horizontalFill'], }; static renderLineItem(option: GridLineType, props: any) { let lineItem; if (React.isValidElement(option)) { lineItem = React.cloneElement(option, props); } else if (_.isFunction(option)) { lineItem = option(props); } else { const { x1, y1, x2, y2, key, ...others } = props; lineItem = ; } return lineItem; } /** * Draw the horizontal grid lines * @param {Array} horizontalPoints either passed in as props or generated from function * @return {Group} Horizontal lines */ renderHorizontal(horizontalPoints: number[]) { const { x, width, horizontal } = this.props; if (!horizontalPoints || !horizontalPoints.length) { return null; } const items = horizontalPoints.map((entry, i) => { const props = { ...this.props, x1: x, y1: entry, x2: x + width, y2: entry, key: `line-${i}`, index: i, }; return CartesianGrid.renderLineItem(horizontal, props); }); return {items}; } /** * Draw vertical grid lines * @param {Array} verticalPoints either passed in as props or generated from function * @return {Group} Vertical lines */ renderVertical(verticalPoints: number[]) { const { y, height, vertical } = this.props; if (!verticalPoints || !verticalPoints.length) { return null; } const items = verticalPoints.map((entry, i) => { const props = { ...this.props, x1: entry, y1: y, x2: entry, y2: y + height, key: `line-${i}`, index: i, }; return CartesianGrid.renderLineItem(vertical, props); }); return {items}; } /** * Draw vertical grid stripes filled by colors * @param {Array} verticalPoints either passed in as props or generated from function * @return {Group} Vertical stripes */ renderVerticalStripes(verticalPoints: number[]) { const { verticalFill } = this.props; if (!verticalFill || !verticalFill.length) { return null; } const { fillOpacity, x, y, width, height } = this.props; const verticalPointsUpdated = verticalPoints.slice().sort((a, b) => a - b); if (x !== verticalPointsUpdated[0]) { verticalPointsUpdated.unshift(0); } const items = verticalPointsUpdated.map((entry, i) => { const lineWidth = verticalPointsUpdated[i + 1] ? verticalPointsUpdated[i + 1] - entry : x + width - entry; if (lineWidth <= 0) { return null; } const colorIndex = i % verticalFill.length; return ( ); }); return {items}; } /** * Draw horizontal grid stripes filled by colors * @param {Array} horizontalPoints either passed in as props or generated from function * @return {Group} Horizontal stripes */ renderHorizontalStripes(horizontalPoints: number[]) { const { horizontalFill } = this.props; if (!horizontalFill || !horizontalFill.length) { return null; } const { fillOpacity, x, y, width, height } = this.props; const horizontalPointsUpdated = horizontalPoints.slice().sort((a, b) => a - b); if (y !== horizontalPointsUpdated[0]) { horizontalPointsUpdated.unshift(0); } const items = horizontalPointsUpdated.map((entry, i) => { const lineHeight = horizontalPointsUpdated[i + 1] ? horizontalPointsUpdated[i + 1] - entry : y + height - entry; if (lineHeight <= 0) { return null; } const colorIndex = i % horizontalFill.length; return ( ); }); return {items}; } renderBackground() { const { fill } = this.props; if (!fill || fill === 'none') { return null; } const { fillOpacity, x, y, width, height } = this.props; return ( ); } render() { const { x, y, width, height, horizontal, vertical, horizontalCoordinatesGenerator, verticalCoordinatesGenerator, xAxis, yAxis, offset, chartWidth, chartHeight, } = this.props; if ( !isNumber(width) || width <= 0 || !isNumber(height) || height <= 0 || !isNumber(x) || x !== +x || !isNumber(y) || y !== +y ) { return null; } let { horizontalPoints, verticalPoints } = this.props; // No horizontal points are specified if ((!horizontalPoints || !horizontalPoints.length) && _.isFunction(horizontalCoordinatesGenerator)) { horizontalPoints = horizontalCoordinatesGenerator({ yAxis, width: chartWidth, height: chartHeight, offset }); } // No vertical points are specified if ((!verticalPoints || !verticalPoints.length) && _.isFunction(verticalCoordinatesGenerator)) { verticalPoints = verticalCoordinatesGenerator({ xAxis, width: chartWidth, height: chartHeight, offset }); } return ( {this.renderBackground()} {horizontal && this.renderHorizontal(horizontalPoints)} {vertical && this.renderVertical(verticalPoints)} {horizontal && this.renderHorizontalStripes(horizontalPoints)} {vertical && this.renderVerticalStripes(verticalPoints)} ); } }