Skip to content

Commit

Permalink
feat(axis): draw grid lines separately from axis tick and customize s…
Browse files Browse the repository at this point in the history
…tyle with config (#8)
  • Loading branch information
emmacunningham authored and markov00 committed Jan 31, 2019
1 parent 1e23a21 commit ab7e974
Show file tree
Hide file tree
Showing 6 changed files with 263 additions and 37 deletions.
49 changes: 39 additions & 10 deletions src/components/react_canvas/axis.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import React from 'react';
import { Group, Line, Rect, Text } from 'react-konva';
import {
AxisTick, AxisTicksDimensions, centerRotationOrigin, getHorizontalAxisTickLineProps,
getTickLabelProps, getVerticalAxisTickLineProps, isHorizontal, isVertical,
AxisTick, AxisTicksDimensions, centerRotationOrigin, getHorizontalAxisGridLineProps,
getHorizontalAxisTickLineProps, getTickLabelProps, getVerticalAxisGridLineProps,
getVerticalAxisTickLineProps, isHorizontal, isVertical, mergeWithDefaultGridLineConfig,
} from '../../lib/axes/axis_utils';
import { AxisSpec, Position } from '../../lib/series/specs';
import { Theme } from '../../lib/themes/theme';
import { DEFAULT_GRID_LINE_CONFIG, Theme } from '../../lib/themes/theme';
import { Dimensions } from '../../lib/utils/dimensions';

interface AxisProps {
Expand Down Expand Up @@ -73,27 +74,31 @@ export class Axis extends React.PureComponent<AxisProps> {
);
}

private renderTickLine = (tick: AxisTick, i: number) => {
private renderGridLine = (tick: AxisTick, i: number) => {
const showGridLines = this.props.axisSpec.showGridLines || false;

if (!showGridLines) {
return null;
}

const {
axisSpec: { tickSize, tickPadding, position },
axisSpec: { tickSize, tickPadding, position, gridLineStyle },
axisTicksDimensions: { maxLabelBboxHeight },
chartDimensions,
chartTheme: { chart: { paddings } },
} = this.props;

const showGridLines = this.props.axisSpec.showGridLines || false;
const config = gridLineStyle ? mergeWithDefaultGridLineConfig(gridLineStyle) : DEFAULT_GRID_LINE_CONFIG;

const lineProps = isVertical(position) ?
getVerticalAxisTickLineProps(
showGridLines,
getVerticalAxisGridLineProps(
position,
tickPadding,
tickSize,
tick.position,
chartDimensions.width,
paddings,
) : getHorizontalAxisTickLineProps(
showGridLines,
) : getHorizontalAxisGridLineProps(
position,
tickPadding,
tickSize,
Expand All @@ -103,6 +108,29 @@ export class Axis extends React.PureComponent<AxisProps> {
paddings,
);

return <Line key={`tick-${i}`} points={lineProps} {...config} />;
}

private renderTickLine = (tick: AxisTick, i: number) => {
const {
axisSpec: { tickSize, tickPadding, position },
axisTicksDimensions: { maxLabelBboxHeight },
} = this.props;

const lineProps = isVertical(position) ?
getVerticalAxisTickLineProps(
position,
tickPadding,
tickSize,
tick.position,
) : getHorizontalAxisTickLineProps(
position,
tickPadding,
tickSize,
tick.position,
maxLabelBboxHeight,
);

return <Line key={`tick-${i}`} points={lineProps} stroke={'gray'} strokeWidth={1} />;
}
private renderAxis = () => {
Expand All @@ -111,6 +139,7 @@ export class Axis extends React.PureComponent<AxisProps> {
<Group x={axisPosition.left} y={axisPosition.top}>
<Group key="lines">{this.renderAxisLine()}</Group>
<Group key="tick-lines">{ticks.map(this.renderTickLine)}</Group>
<Group key="grid-lines">{ticks.map(this.renderGridLine)}</Group>
<Group key="ticks">
{ticks.filter((tick) => tick.label !== null).map(this.renderTickLabel)}
</Group>
Expand Down
114 changes: 112 additions & 2 deletions src/lib/axes/axis_utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,21 @@ import { XDomain } from '../series/domains/x_domain';
import { YDomain } from '../series/domains/y_domain';
import { Position } from '../series/specs';
import { DEFAULT_THEME } from '../themes/theme';
import { Margins } from '../utils/dimensions';
import { getAxisId, getGroupId } from '../utils/ids';
import { ScaleType } from '../utils/scales/scales';
import {
centerRotationOrigin,
computeAxisTicksDimensions,
computeRotatedLabelDimensions,
getAvailableTicks,
getHorizontalAxisGridLineProps,
getHorizontalAxisTickLineProps,
getMinMaxRange,
getScaleForAxisSpec,
getTickLabelProps,
getVerticalAxisGridLineProps,
getVerticalAxisTickLineProps,
getVisibleTicks,
} from './axis_utils';
import { SvgTextBBoxCalculator } from './svg_text_bbox_calculator';
Expand All @@ -34,6 +39,7 @@ describe('Axis computational utils', () => {
toJSON: () => '',
};
const originalGetBBox = SVGElement.prototype.getBoundingClientRect;

beforeEach(
() =>
(SVGElement.prototype.getBoundingClientRect = function() {
Expand Down Expand Up @@ -121,7 +127,6 @@ describe('Axis computational utils', () => {
expect(dims45.height).toBeCloseTo(Math.sqrt(2));
});

// TODO: these tests appear to be failing (also on master)
test('should generate a valid scale', () => {
const scale = getScaleForAxisSpec(verticalAxisSpec, xDomain, [yDomain], 0, 0, 100, 0);
expect(scale).toBeDefined();
Expand All @@ -131,7 +136,6 @@ describe('Axis computational utils', () => {
expect(scale!.ticks()).toEqual([0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1]);
});

// TODO: these tests appear to be failing (also on master)
test('should compute available ticks', () => {
const scale = getScaleForAxisSpec(verticalAxisSpec, xDomain, [yDomain], 0, 0, 100, 0);
const axisPositions = getAvailableTicks(verticalAxisSpec, scale!, 0);
Expand Down Expand Up @@ -403,4 +407,110 @@ describe('Axis computational utils', () => {
verticalAlign: 'middle',
});
});

test('should compute axis tick line positions', () => {
const tickPadding = 5;
const tickSize = 10;
const tickPosition = 10;
const maxLabelBboxHeight = 20;

const leftAxisTickLinePositions = getVerticalAxisTickLineProps(
Position.Left,
tickPadding,
tickSize,
tickPosition,
);

expect(leftAxisTickLinePositions).toEqual([5, 10, 15, 10]);

const rightAxisTickLinePositions = getVerticalAxisTickLineProps(
Position.Right,
tickPadding,
tickSize,
tickPosition,
);

expect(rightAxisTickLinePositions).toEqual([0, 10, 10, 10]);

const topAxisTickLinePositions = getHorizontalAxisTickLineProps(
Position.Top,
tickPadding,
tickSize,
tickPosition,
maxLabelBboxHeight,
);

expect(topAxisTickLinePositions).toEqual([10, 25, 10, 35]);

const bottomAxisTickLinePositions = getHorizontalAxisTickLineProps(
Position.Bottom,
tickPadding,
tickSize,
tickPosition,
maxLabelBboxHeight,
);

expect(bottomAxisTickLinePositions).toEqual([10, 0, 10, 10]);
});

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,
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]);

const bottomAxisGridLinePositions = getHorizontalAxisGridLineProps(
Position.Bottom,
tickPadding,
tickSize,
tickPosition,
maxLabelBboxHeight,
chartHeight,
paddings,
);

expect(bottomAxisGridLinePositions).toEqual([10, -209, 10, -9]);
});
});
70 changes: 48 additions & 22 deletions src/lib/axes/axis_utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { XDomain } from '../series/domains/x_domain';
import { YDomain } from '../series/domains/y_domain';
import { computeXScale, computeYScales } from '../series/scales';
import { AxisSpec, Position, Rotation, TickFormatter } from '../series/specs';
import { AxisConfig, Theme } from '../themes/theme';
import { AxisConfig, DEFAULT_GRID_LINE_CONFIG, GridLineConfig, Theme } from '../themes/theme';
import { Dimensions, Margins } from '../utils/dimensions';
import { Domain } from '../utils/domain';
import { AxisId } from '../utils/ids';
Expand Down Expand Up @@ -242,54 +242,80 @@ export function getTickLabelProps(
}

export function getVerticalAxisTickLineProps(
showGridLine: boolean,
position: Position,
tickPadding: number,
tickSize: number,
tickPosition: number,
chartWidth: number,
paddings: Margins,
): number[] {
const isLeftAxis = position === Position.Left;
const y = tickPosition;
const x1 = isLeftAxis ? tickPadding : 0;
const x2 = isLeftAxis ? tickSize + tickPadding : tickSize;

if (showGridLine) {
if (isLeftAxis) {
return [x1, y, x2 + chartWidth + paddings.left, y];
}

return [x1 - chartWidth - paddings.right, y, x2, y];
}

return [x1, y, x2, y];
}

export function getHorizontalAxisTickLineProps(
showGridLine: boolean,
position: Position,
tickPadding: number,
tickSize: number,
tickPosition: number,
labelHeight: number,
chartHeight: number,
paddings: Margins,
): number[] {
const isTopAxis = position === Position.Top;
const x = tickPosition;
const y1 = isTopAxis ? labelHeight + tickPadding : 0;
const y2 = isTopAxis ? labelHeight + tickPadding + tickSize : tickSize;

if (showGridLine) {
if (isTopAxis) {
return [x, y1, x, y2 + chartHeight + paddings.top];
}
return [x, y1, x, y2];
}

return [x, y1 - chartHeight - paddings.bottom, x, y2];
}
export function getVerticalAxisGridLineProps(
position: Position,
tickPadding: number,
tickSize: number,
tickPosition: number,
chartWidth: number,
paddings: Margins,
): number[] {
const isLeftAxis = position === Position.Left;
const y = tickPosition;

return [x, y1, x, y2];
const leftX1 = tickSize + tickPadding + paddings.left;
const rightX1 = - chartWidth - paddings.right;

const x1 = isLeftAxis ? leftX1 : rightX1;

return [x1, y, x1 + chartWidth, y];
}

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];
}

export function mergeWithDefaultGridLineConfig(config: GridLineConfig): GridLineConfig {
return {
stroke: config.stroke || DEFAULT_GRID_LINE_CONFIG.stroke,
strokeWidth: config.strokeWidth || DEFAULT_GRID_LINE_CONFIG.strokeWidth,
opacity: config.opacity || DEFAULT_GRID_LINE_CONFIG.opacity,
dash: config.dash || DEFAULT_GRID_LINE_CONFIG.dash,
};
}

export function getMinMaxRange(
Expand Down
3 changes: 3 additions & 0 deletions src/lib/series/specs.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { GridLineConfig } from '../themes/theme';
import { Accessor } from '../utils/accessor';
import { Domain } from '../utils/domain';
import { AxisId, GroupId, SpecId } from '../utils/ids';
Expand Down Expand Up @@ -101,6 +102,8 @@ export type AreaSeriesSpec = BasicSeriesSpec & {
export interface AxisSpec {
/** The ID of the spec, generated via getSpecId method */
id: AxisId;
/** Style options for grid line */
gridLineStyle?: GridLineConfig;
/** The ID of the axis group, generated via getGroupId method
* @default __global__
*/
Expand Down
Loading

0 comments on commit ab7e974

Please sign in to comment.