diff --git a/.storybook/config.ts b/.storybook/config.ts index aad38306cec..5ade6422840 100644 --- a/.storybook/config.ts +++ b/.storybook/config.ts @@ -30,6 +30,7 @@ function loadStories() { require('../src/stories/interactions.tsx'); require('../src/stories/rotations.tsx'); require('../src/stories/styling.tsx'); + require('../src/stories/grid.tsx'); } configure(loadStories, module); diff --git a/src/components/react_canvas/axis.tsx b/src/components/react_canvas/axis.tsx index 8537d527f04..e2233e8a035 100644 --- a/src/components/react_canvas/axis.tsx +++ b/src/components/react_canvas/axis.tsx @@ -1,12 +1,12 @@ import React from 'react'; import { Group, Line, Rect, Text } from 'react-konva'; import { - AxisTick, AxisTicksDimensions, centerRotationOrigin, getHorizontalAxisGridLineProps, - getHorizontalAxisTickLineProps, getTickLabelProps, getVerticalAxisGridLineProps, - getVerticalAxisTickLineProps, isHorizontal, isVertical, mergeWithDefaultGridLineConfig, + AxisTick, AxisTicksDimensions, centerRotationOrigin, + getHorizontalAxisTickLineProps, getTickLabelProps, + getVerticalAxisTickLineProps, isHorizontal, isVertical, } from '../../lib/axes/axis_utils'; import { AxisSpec, Position } from '../../lib/series/specs'; -import { DEFAULT_GRID_LINE_CONFIG, Theme } from '../../lib/themes/theme'; +import { Theme } from '../../lib/themes/theme'; import { Dimensions } from '../../lib/utils/dimensions'; interface AxisProps { @@ -74,43 +74,6 @@ export class Axis extends React.PureComponent { ); } - private renderGridLine = (tick: AxisTick, i: number) => { - const showGridLines = this.props.axisSpec.showGridLines || false; - - if (!showGridLines) { - return null; - } - - const { - axisSpec: { tickSize, tickPadding, position, gridLineStyle }, - axisTicksDimensions: { maxLabelBboxHeight }, - chartDimensions, - chartTheme: { chart: { paddings } }, - } = this.props; - - const config = gridLineStyle ? mergeWithDefaultGridLineConfig(gridLineStyle) : DEFAULT_GRID_LINE_CONFIG; - - const lineProps = isVertical(position) ? - getVerticalAxisGridLineProps( - position, - tickPadding, - tickSize, - tick.position, - chartDimensions.width, - paddings, - ) : getHorizontalAxisGridLineProps( - position, - tickPadding, - tickSize, - tick.position, - maxLabelBboxHeight, - chartDimensions.height, - paddings, - ); - - return ; - } - private renderTickLine = (tick: AxisTick, i: number) => { const { axisSpec: { tickSize, tickPadding, position }, @@ -139,7 +102,6 @@ export class Axis extends React.PureComponent { {this.renderAxisLine()} {ticks.map(this.renderTickLine)} - {ticks.map(this.renderGridLine)} {ticks.filter((tick) => tick.label !== null).map(this.renderTickLabel)} diff --git a/src/components/react_canvas/grid.tsx b/src/components/react_canvas/grid.tsx new file mode 100644 index 00000000000..6c97a25812e --- /dev/null +++ b/src/components/react_canvas/grid.tsx @@ -0,0 +1,39 @@ +import React from 'react'; +import { Group, Line } from 'react-konva'; +import { + AxisLinePosition, mergeWithDefaultGridLineConfig, +} from '../../lib/axes/axis_utils'; +import { DEFAULT_GRID_LINE_CONFIG, GridLineConfig } from '../../lib/themes/theme'; +import { Dimensions } from '../../lib/utils/dimensions'; + +interface GridProps { + chartDimensions: Dimensions; + debug: boolean; + gridLineStyle: GridLineConfig | undefined; + linesPositions: AxisLinePosition[]; +} + +export class Grid extends React.PureComponent { + render() { + return this.renderGrid(); + } + private renderGridLine = (linePosition: AxisLinePosition, i: number) => { + const { + gridLineStyle, + } = this.props; + + const config = gridLineStyle ? mergeWithDefaultGridLineConfig(gridLineStyle) : DEFAULT_GRID_LINE_CONFIG; + + return ; + } + + private renderGrid = () => { + const { chartDimensions, linesPositions } = this.props; + + return ( + + {linesPositions.map(this.renderGridLine)} + + ); + } +} diff --git a/src/components/react_canvas/reactive_chart.tsx b/src/components/react_canvas/reactive_chart.tsx index 580e186386f..eb0a3297179 100644 --- a/src/components/react_canvas/reactive_chart.tsx +++ b/src/components/react_canvas/reactive_chart.tsx @@ -7,6 +7,7 @@ import { BrushExtent } from '../../state/utils'; import { AreaGeometries } from './area_geometries'; import { Axis } from './axis'; import { BarGeometries } from './bar_geometries'; +import { Grid } from './grid'; import { LineGeometries } from './line_geometries'; interface ReactiveChartProps { @@ -161,6 +162,33 @@ class Chart extends React.Component { }); return axesComponents; } + + renderGrids = () => { + const { + axesGridLinesPositions, + axesSpecs, + chartDimensions, + debug, + } = this.props.chartStore!; + + const gridComponents: JSX.Element[] = []; + axesGridLinesPositions.forEach((axisGridLinesPositions, axisId) => { + const axisSpec = axesSpecs.get(axisId); + if (axisSpec && axisGridLinesPositions.length > 0) { + gridComponents.push( + , + ); + } + }); + return gridComponents; + } + renderBrushTool = () => { const { brushing, brushStart, brushEnd } = this.state; const { chartDimensions, chartRotation, chartTransform } = this.props.chartStore!; @@ -243,6 +271,7 @@ class Chart extends React.Component { ? chartDimensions.width : chartDimensions.height, }; + let brushProps = {}; const isBrushEnabled = this.props.chartStore!.isBrushEnabled(); if (isBrushEnabled) { @@ -253,6 +282,13 @@ class Chart extends React.Component { }; } + const gridClippings = { + clipX: chartDimensions.left, + clipY: chartDimensions.top, + clipWidth: chartDimensions.width, + clipHeight: chartDimensions.height, + }; + return (
{ }} {...brushProps} > + {this.renderGrids()} + { }); test('should compute axis grid line positions', () => { - const tickPadding = 5; - const tickSize = 10; const tickPosition = 10; - const maxLabelBboxHeight = 20; const chartWidth = 100; const chartHeight = 200; - const paddings: Margins = { - top: 3, - left: 6, - bottom: 9, - right: 12, - }; - - const leftAxisGridLinePositions = getVerticalAxisGridLineProps( - Position.Left, - tickPadding, - tickSize, - tickPosition, - chartWidth, - paddings, - ); - expect(leftAxisGridLinePositions).toEqual([21, 10, 121, 10]); - - const rightAxisGridLinePositions = getVerticalAxisGridLineProps( - Position.Right, - tickPadding, - tickSize, + const verticalAxisGridLinePositions = getVerticalAxisGridLineProps( tickPosition, chartWidth, - paddings, - ); - - expect(rightAxisGridLinePositions).toEqual([-112, 10, -12, 10]); - - const topAxisGridLinePositions = getHorizontalAxisGridLineProps( - Position.Top, - tickPadding, - tickSize, - tickPosition, - maxLabelBboxHeight, - chartHeight, - paddings, ); - expect(topAxisGridLinePositions).toEqual([10, 38, 10, 238]); + expect(verticalAxisGridLinePositions).toEqual([0, 10, 100, 10]); - const bottomAxisGridLinePositions = getHorizontalAxisGridLineProps( - Position.Bottom, - tickPadding, - tickSize, + const horizontalAxisGridLinePositions = getHorizontalAxisGridLineProps( tickPosition, - maxLabelBboxHeight, chartHeight, - paddings, ); - expect(bottomAxisGridLinePositions).toEqual([10, -209, 10, -9]); + expect(horizontalAxisGridLinePositions).toEqual([10, 0, 10, 200]); }); }); diff --git a/src/lib/axes/axis_utils.ts b/src/lib/axes/axis_utils.ts index 2fe6eb78a9d..6d16434174a 100644 --- a/src/lib/axes/axis_utils.ts +++ b/src/lib/axes/axis_utils.ts @@ -10,6 +10,8 @@ import { AxisId } from '../utils/ids'; import { Scale, ScaleType } from '../utils/scales/scales'; import { BBox, BBoxCalculator } from './bbox_calculator'; +export type AxisLinePosition = [number, number, number, number]; + export interface AxisTick { value: number | string; label: string; @@ -246,7 +248,7 @@ export function getVerticalAxisTickLineProps( tickPadding: number, tickSize: number, tickPosition: number, -): number[] { +): AxisLinePosition { const isLeftAxis = position === Position.Left; const y = tickPosition; const x1 = isLeftAxis ? tickPadding : 0; @@ -261,7 +263,7 @@ export function getHorizontalAxisTickLineProps( tickSize: number, tickPosition: number, labelHeight: number, -): number[] { +): AxisLinePosition { const isTopAxis = position === Position.Top; const x = tickPosition; const y1 = isTopAxis ? labelHeight + tickPadding : 0; @@ -271,42 +273,17 @@ export function getHorizontalAxisTickLineProps( } export function getVerticalAxisGridLineProps( - position: Position, - tickPadding: number, - tickSize: number, tickPosition: number, chartWidth: number, - paddings: Margins, -): number[] { - const isLeftAxis = position === Position.Left; - const y = tickPosition; - - const leftX1 = tickSize + tickPadding + paddings.left; - const rightX1 = - chartWidth - paddings.right; - - const x1 = isLeftAxis ? leftX1 : rightX1; - - return [x1, y, x1 + chartWidth, y]; +): AxisLinePosition { + return [0, tickPosition, chartWidth, tickPosition]; } export function getHorizontalAxisGridLineProps( - position: Position, - tickPadding: number, - tickSize: number, tickPosition: number, - labelHeight: number, chartHeight: number, - paddings: Margins, -): number[] { - const isTopAxis = position === Position.Top; - const x = tickPosition; - - const topY1 = labelHeight + tickPadding + tickSize + paddings.top; - const bottomY1 = - chartHeight - paddings.bottom; - - const y1 = isTopAxis ? topY1 : bottomY1; - - return [x, y1, x, y1 + chartHeight]; +): AxisLinePosition { + return [tickPosition, 0, tickPosition, chartHeight]; } export function mergeWithDefaultGridLineConfig(config: GridLineConfig): GridLineConfig { @@ -501,6 +478,7 @@ export function getAxisTicksPositions( const axisPositions: Map = new Map(); const axisVisibleTicks: Map = new Map(); const axisTicks: Map = new Map(); + const axisGridLinesPositions: Map = new Map(); let cumTopSum = 0; let cumBottomSum = chartConfig.paddings.bottom; @@ -560,6 +538,14 @@ export function getAxisTicksPositions( chartRotation, ); + if (axisSpec.showGridLines) { + const isVerticalAxis = isVertical(axisSpec.position); + const gridLines = visibleTicks.map((tick: AxisTick): AxisLinePosition => { + return computeAxisGridLinePositions(isVerticalAxis, tick.position, chartDimensions); + }); + axisGridLinesPositions.set(id, gridLines); + } + const { titleFontSize, titlePadding } = chartTheme.axes; const axisTitleHeight = titleFontSize + titlePadding; @@ -587,9 +573,27 @@ export function getAxisTicksPositions( axisPositions, axisTicks, axisVisibleTicks, + axisGridLinesPositions, }; } +function computeAxisGridLinePositions( + isVerticalAxis: boolean, + tickPosition: number, + chartDimensions: Dimensions, +): AxisLinePosition { + const positions = isVerticalAxis ? + getVerticalAxisGridLineProps( + tickPosition, + chartDimensions.width, + ) : getHorizontalAxisGridLineProps( + tickPosition, + chartDimensions.height, + ); + + return positions; +} + function getVerticalDomain( xDomain: XDomain, yDomain: YDomain[], diff --git a/src/state/chart_state.ts b/src/state/chart_state.ts index df6716301b9..ed730154ff9 100644 --- a/src/state/chart_state.ts +++ b/src/state/chart_state.ts @@ -1,5 +1,6 @@ import { action, observable } from 'mobx'; import { + AxisLinePosition, AxisTick, AxisTicksDimensions, computeAxisTicksDimensions, @@ -113,6 +114,7 @@ export class ChartStore { axesPositions: Map = new Map(); // computed axesVisibleTicks: Map = new Map(); // computed axesTicks: Map = new Map(); // computed + axesGridLinesPositions: Map = new Map(); // computed seriesSpecs: Map = new Map(); // readed from jsx @@ -374,6 +376,7 @@ export class ChartStore { this.axesPositions = axisTicksPositions.axisPositions; this.axesTicks = axisTicksPositions.axisTicks; this.axesVisibleTicks = axisTicksPositions.axisVisibleTicks; + this.axesGridLinesPositions = axisTicksPositions.axisGridLinesPositions; // if (glyphsCount > MAX_ANIMATABLE_GLYPHS) { // this.canDataBeAnimated = false; // } else { diff --git a/src/stories/grid.tsx b/src/stories/grid.tsx new file mode 100644 index 00000000000..1c19561664f --- /dev/null +++ b/src/stories/grid.tsx @@ -0,0 +1,155 @@ +import { boolean, color, number } from '@storybook/addon-knobs'; +import { storiesOf } from '@storybook/react'; +import React from 'react'; +import { Axis, BarSeries, Chart, getAxisId, getSpecId, LineSeries, Position, ScaleType, Settings } from '..'; +import { GridLineConfig } from '../lib/themes/theme'; +import { getGroupId } from '../lib/utils/ids'; + +function generateGridLineConfig(group: string): GridLineConfig { + const groupId = `${group} axis`; + + return { + stroke: color(`${groupId} grid line stroke color`, 'purple', groupId), + strokeWidth: number(`${groupId} grid line stroke width`, 1, { + range: true, + min: 0, + max: 10, + step: 1, + }, groupId), + opacity: number(`${groupId} grid line stroke opacity`, 1, { + range: true, + min: 0, + max: 1, + step: 0.01, + }, groupId), + dash: [ + number(`${groupId} grid line dash length`, 1, { + range: true, + min: 0, + max: 10, + step: 1, + }, groupId), + number(`${groupId} grid line dash spacing`, 1, { + range: true, + min: 0, + max: 10, + step: 1, + }, groupId), + ], + }; +} + +storiesOf('Grids', module) + .add('basic', () => { + const leftAxisGridLineConfig = generateGridLineConfig(Position.Left); + const rightAxisGridLineConfig = generateGridLineConfig(Position.Right); + const topAxisGridLineConfig = generateGridLineConfig(Position.Top); + const bottomAxisGridLineConfig = generateGridLineConfig(Position.Bottom); + + return ( + + + + Number(d).toFixed(2)} + showGridLines={boolean('show left axis grid lines', false, 'left axis')} + gridLineStyle={leftAxisGridLineConfig} + /> + + Number(d).toFixed(2)} + showGridLines={boolean('show right axis grid lines', false, 'right axis')} + gridLineStyle={rightAxisGridLineConfig} + /> + + + + ); + }) + .add('multiple axes with the same position', () => { + const leftAxisGridLineConfig = generateGridLineConfig(Position.Left); + const leftAxisGridLineConfig2 = generateGridLineConfig(`${Position.Left}2`); + + return ( + + + Number(d).toFixed(2)} + showGridLines={boolean('show left axis grid lines', false, 'left axis')} + gridLineStyle={leftAxisGridLineConfig} + /> + Number(d).toFixed(2)} + showGridLines={boolean('show left axis 2 grid lines', false, 'left2 axis')} + gridLineStyle={leftAxisGridLineConfig2} + /> + + + + ); + }); diff --git a/src/stories/styling.tsx b/src/stories/styling.tsx index 0a7c9803160..f4f8f619550 100644 --- a/src/stories/styling.tsx +++ b/src/stories/styling.tsx @@ -2,7 +2,7 @@ import { boolean, number, select } from '@storybook/addon-knobs'; import { storiesOf } from '@storybook/react'; import React from 'react'; import { Axis, BarSeries, Chart, getAxisId, getSpecId, Position, ScaleType, Settings } from '..'; -import { GridLineConfig, PartialTheme } from '../lib/themes/theme'; +import { PartialTheme } from '../lib/themes/theme'; function createThemeAction(title: string, min: number, max: number, value: number) { return number(title, value, { @@ -32,36 +32,6 @@ storiesOf('Stylings', module) }, }; - const leftAxisGridLine: GridLineConfig = { - stroke: 'purple', - strokeWidth: number('left axis grid line stroke width', 1, { - range: true, - min: 0, - max: 10, - step: 1, - }), - opacity: number('left axis grid line stroke opacity', 1, { - range: true, - min: 0, - max: 1, - step: 0.01, - }), - dash: [ - number('left axis grid line dash length', 1, { - range: true, - min: 0, - max: 10, - step: 1, - }), - number('left axis grid line dash spacing', 1, { - range: true, - min: 0, - max: 10, - step: 1, - }), - ], - }; - return ( @@ -78,7 +48,6 @@ storiesOf('Stylings', module) position={Position.Left} tickFormat={(d) => Number(d).toFixed(2)} showGridLines={boolean('show left axis grid lines', false)} - gridLineStyle={leftAxisGridLine} />