Skip to content

Commit ab7e974

Browse files
emmacunninghammarkov00
authored andcommitted
feat(axis): draw grid lines separately from axis tick and customize style with config (#8)
1 parent 1e23a21 commit ab7e974

File tree

6 files changed

+263
-37
lines changed

6 files changed

+263
-37
lines changed

src/components/react_canvas/axis.tsx

Lines changed: 39 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import React from 'react';
22
import { Group, Line, Rect, Text } from 'react-konva';
33
import {
4-
AxisTick, AxisTicksDimensions, centerRotationOrigin, getHorizontalAxisTickLineProps,
5-
getTickLabelProps, getVerticalAxisTickLineProps, isHorizontal, isVertical,
4+
AxisTick, AxisTicksDimensions, centerRotationOrigin, getHorizontalAxisGridLineProps,
5+
getHorizontalAxisTickLineProps, getTickLabelProps, getVerticalAxisGridLineProps,
6+
getVerticalAxisTickLineProps, isHorizontal, isVertical, mergeWithDefaultGridLineConfig,
67
} from '../../lib/axes/axis_utils';
78
import { AxisSpec, Position } from '../../lib/series/specs';
8-
import { Theme } from '../../lib/themes/theme';
9+
import { DEFAULT_GRID_LINE_CONFIG, Theme } from '../../lib/themes/theme';
910
import { Dimensions } from '../../lib/utils/dimensions';
1011

1112
interface AxisProps {
@@ -73,27 +74,31 @@ export class Axis extends React.PureComponent<AxisProps> {
7374
);
7475
}
7576

76-
private renderTickLine = (tick: AxisTick, i: number) => {
77+
private renderGridLine = (tick: AxisTick, i: number) => {
78+
const showGridLines = this.props.axisSpec.showGridLines || false;
79+
80+
if (!showGridLines) {
81+
return null;
82+
}
83+
7784
const {
78-
axisSpec: { tickSize, tickPadding, position },
85+
axisSpec: { tickSize, tickPadding, position, gridLineStyle },
7986
axisTicksDimensions: { maxLabelBboxHeight },
8087
chartDimensions,
8188
chartTheme: { chart: { paddings } },
8289
} = this.props;
8390

84-
const showGridLines = this.props.axisSpec.showGridLines || false;
91+
const config = gridLineStyle ? mergeWithDefaultGridLineConfig(gridLineStyle) : DEFAULT_GRID_LINE_CONFIG;
8592

8693
const lineProps = isVertical(position) ?
87-
getVerticalAxisTickLineProps(
88-
showGridLines,
94+
getVerticalAxisGridLineProps(
8995
position,
9096
tickPadding,
9197
tickSize,
9298
tick.position,
9399
chartDimensions.width,
94100
paddings,
95-
) : getHorizontalAxisTickLineProps(
96-
showGridLines,
101+
) : getHorizontalAxisGridLineProps(
97102
position,
98103
tickPadding,
99104
tickSize,
@@ -103,6 +108,29 @@ export class Axis extends React.PureComponent<AxisProps> {
103108
paddings,
104109
);
105110

111+
return <Line key={`tick-${i}`} points={lineProps} {...config} />;
112+
}
113+
114+
private renderTickLine = (tick: AxisTick, i: number) => {
115+
const {
116+
axisSpec: { tickSize, tickPadding, position },
117+
axisTicksDimensions: { maxLabelBboxHeight },
118+
} = this.props;
119+
120+
const lineProps = isVertical(position) ?
121+
getVerticalAxisTickLineProps(
122+
position,
123+
tickPadding,
124+
tickSize,
125+
tick.position,
126+
) : getHorizontalAxisTickLineProps(
127+
position,
128+
tickPadding,
129+
tickSize,
130+
tick.position,
131+
maxLabelBboxHeight,
132+
);
133+
106134
return <Line key={`tick-${i}`} points={lineProps} stroke={'gray'} strokeWidth={1} />;
107135
}
108136
private renderAxis = () => {
@@ -111,6 +139,7 @@ export class Axis extends React.PureComponent<AxisProps> {
111139
<Group x={axisPosition.left} y={axisPosition.top}>
112140
<Group key="lines">{this.renderAxisLine()}</Group>
113141
<Group key="tick-lines">{ticks.map(this.renderTickLine)}</Group>
142+
<Group key="grid-lines">{ticks.map(this.renderGridLine)}</Group>
114143
<Group key="ticks">
115144
{ticks.filter((tick) => tick.label !== null).map(this.renderTickLabel)}
116145
</Group>

src/lib/axes/axis_utils.test.ts

Lines changed: 112 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,21 @@ import { XDomain } from '../series/domains/x_domain';
22
import { YDomain } from '../series/domains/y_domain';
33
import { Position } from '../series/specs';
44
import { DEFAULT_THEME } from '../themes/theme';
5+
import { Margins } from '../utils/dimensions';
56
import { getAxisId, getGroupId } from '../utils/ids';
67
import { ScaleType } from '../utils/scales/scales';
78
import {
89
centerRotationOrigin,
910
computeAxisTicksDimensions,
1011
computeRotatedLabelDimensions,
1112
getAvailableTicks,
13+
getHorizontalAxisGridLineProps,
14+
getHorizontalAxisTickLineProps,
1215
getMinMaxRange,
1316
getScaleForAxisSpec,
1417
getTickLabelProps,
18+
getVerticalAxisGridLineProps,
19+
getVerticalAxisTickLineProps,
1520
getVisibleTicks,
1621
} from './axis_utils';
1722
import { SvgTextBBoxCalculator } from './svg_text_bbox_calculator';
@@ -34,6 +39,7 @@ describe('Axis computational utils', () => {
3439
toJSON: () => '',
3540
};
3641
const originalGetBBox = SVGElement.prototype.getBoundingClientRect;
42+
3743
beforeEach(
3844
() =>
3945
(SVGElement.prototype.getBoundingClientRect = function() {
@@ -121,7 +127,6 @@ describe('Axis computational utils', () => {
121127
expect(dims45.height).toBeCloseTo(Math.sqrt(2));
122128
});
123129

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

134-
// TODO: these tests appear to be failing (also on master)
135139
test('should compute available ticks', () => {
136140
const scale = getScaleForAxisSpec(verticalAxisSpec, xDomain, [yDomain], 0, 0, 100, 0);
137141
const axisPositions = getAvailableTicks(verticalAxisSpec, scale!, 0);
@@ -403,4 +407,110 @@ describe('Axis computational utils', () => {
403407
verticalAlign: 'middle',
404408
});
405409
});
410+
411+
test('should compute axis tick line positions', () => {
412+
const tickPadding = 5;
413+
const tickSize = 10;
414+
const tickPosition = 10;
415+
const maxLabelBboxHeight = 20;
416+
417+
const leftAxisTickLinePositions = getVerticalAxisTickLineProps(
418+
Position.Left,
419+
tickPadding,
420+
tickSize,
421+
tickPosition,
422+
);
423+
424+
expect(leftAxisTickLinePositions).toEqual([5, 10, 15, 10]);
425+
426+
const rightAxisTickLinePositions = getVerticalAxisTickLineProps(
427+
Position.Right,
428+
tickPadding,
429+
tickSize,
430+
tickPosition,
431+
);
432+
433+
expect(rightAxisTickLinePositions).toEqual([0, 10, 10, 10]);
434+
435+
const topAxisTickLinePositions = getHorizontalAxisTickLineProps(
436+
Position.Top,
437+
tickPadding,
438+
tickSize,
439+
tickPosition,
440+
maxLabelBboxHeight,
441+
);
442+
443+
expect(topAxisTickLinePositions).toEqual([10, 25, 10, 35]);
444+
445+
const bottomAxisTickLinePositions = getHorizontalAxisTickLineProps(
446+
Position.Bottom,
447+
tickPadding,
448+
tickSize,
449+
tickPosition,
450+
maxLabelBboxHeight,
451+
);
452+
453+
expect(bottomAxisTickLinePositions).toEqual([10, 0, 10, 10]);
454+
});
455+
456+
test('should compute axis grid line positions', () => {
457+
const tickPadding = 5;
458+
const tickSize = 10;
459+
const tickPosition = 10;
460+
const maxLabelBboxHeight = 20;
461+
const chartWidth = 100;
462+
const chartHeight = 200;
463+
const paddings: Margins = {
464+
top: 3,
465+
left: 6,
466+
bottom: 9,
467+
right: 12,
468+
};
469+
470+
const leftAxisGridLinePositions = getVerticalAxisGridLineProps(
471+
Position.Left,
472+
tickPadding,
473+
tickSize,
474+
tickPosition,
475+
chartWidth,
476+
paddings,
477+
);
478+
479+
expect(leftAxisGridLinePositions).toEqual([21, 10, 121, 10]);
480+
481+
const rightAxisGridLinePositions = getVerticalAxisGridLineProps(
482+
Position.Right,
483+
tickPadding,
484+
tickSize,
485+
tickPosition,
486+
chartWidth,
487+
paddings,
488+
);
489+
490+
expect(rightAxisGridLinePositions).toEqual([-112, 10, -12, 10]);
491+
492+
const topAxisGridLinePositions = getHorizontalAxisGridLineProps(
493+
Position.Top,
494+
tickPadding,
495+
tickSize,
496+
tickPosition,
497+
maxLabelBboxHeight,
498+
chartHeight,
499+
paddings,
500+
);
501+
502+
expect(topAxisGridLinePositions).toEqual([10, 38, 10, 238]);
503+
504+
const bottomAxisGridLinePositions = getHorizontalAxisGridLineProps(
505+
Position.Bottom,
506+
tickPadding,
507+
tickSize,
508+
tickPosition,
509+
maxLabelBboxHeight,
510+
chartHeight,
511+
paddings,
512+
);
513+
514+
expect(bottomAxisGridLinePositions).toEqual([10, -209, 10, -9]);
515+
});
406516
});

src/lib/axes/axis_utils.ts

Lines changed: 48 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { XDomain } from '../series/domains/x_domain';
33
import { YDomain } from '../series/domains/y_domain';
44
import { computeXScale, computeYScales } from '../series/scales';
55
import { AxisSpec, Position, Rotation, TickFormatter } from '../series/specs';
6-
import { AxisConfig, Theme } from '../themes/theme';
6+
import { AxisConfig, DEFAULT_GRID_LINE_CONFIG, GridLineConfig, Theme } from '../themes/theme';
77
import { Dimensions, Margins } from '../utils/dimensions';
88
import { Domain } from '../utils/domain';
99
import { AxisId } from '../utils/ids';
@@ -242,54 +242,80 @@ export function getTickLabelProps(
242242
}
243243

244244
export function getVerticalAxisTickLineProps(
245-
showGridLine: boolean,
246245
position: Position,
247246
tickPadding: number,
248247
tickSize: number,
249248
tickPosition: number,
250-
chartWidth: number,
251-
paddings: Margins,
252249
): number[] {
253250
const isLeftAxis = position === Position.Left;
254251
const y = tickPosition;
255252
const x1 = isLeftAxis ? tickPadding : 0;
256253
const x2 = isLeftAxis ? tickSize + tickPadding : tickSize;
257254

258-
if (showGridLine) {
259-
if (isLeftAxis) {
260-
return [x1, y, x2 + chartWidth + paddings.left, y];
261-
}
262-
263-
return [x1 - chartWidth - paddings.right, y, x2, y];
264-
}
265-
266255
return [x1, y, x2, y];
267256
}
268257

269258
export function getHorizontalAxisTickLineProps(
270-
showGridLine: boolean,
271259
position: Position,
272260
tickPadding: number,
273261
tickSize: number,
274262
tickPosition: number,
275263
labelHeight: number,
276-
chartHeight: number,
277-
paddings: Margins,
278264
): number[] {
279265
const isTopAxis = position === Position.Top;
280266
const x = tickPosition;
281267
const y1 = isTopAxis ? labelHeight + tickPadding : 0;
282268
const y2 = isTopAxis ? labelHeight + tickPadding + tickSize : tickSize;
283269

284-
if (showGridLine) {
285-
if (isTopAxis) {
286-
return [x, y1, x, y2 + chartHeight + paddings.top];
287-
}
270+
return [x, y1, x, y2];
271+
}
288272

289-
return [x, y1 - chartHeight - paddings.bottom, x, y2];
290-
}
273+
export function getVerticalAxisGridLineProps(
274+
position: Position,
275+
tickPadding: number,
276+
tickSize: number,
277+
tickPosition: number,
278+
chartWidth: number,
279+
paddings: Margins,
280+
): number[] {
281+
const isLeftAxis = position === Position.Left;
282+
const y = tickPosition;
291283

292-
return [x, y1, x, y2];
284+
const leftX1 = tickSize + tickPadding + paddings.left;
285+
const rightX1 = - chartWidth - paddings.right;
286+
287+
const x1 = isLeftAxis ? leftX1 : rightX1;
288+
289+
return [x1, y, x1 + chartWidth, y];
290+
}
291+
292+
export function getHorizontalAxisGridLineProps(
293+
position: Position,
294+
tickPadding: number,
295+
tickSize: number,
296+
tickPosition: number,
297+
labelHeight: number,
298+
chartHeight: number,
299+
paddings: Margins,
300+
): number[] {
301+
const isTopAxis = position === Position.Top;
302+
const x = tickPosition;
303+
304+
const topY1 = labelHeight + tickPadding + tickSize + paddings.top;
305+
const bottomY1 = - chartHeight - paddings.bottom;
306+
307+
const y1 = isTopAxis ? topY1 : bottomY1;
308+
309+
return [x, y1, x, y1 + chartHeight];
310+
}
311+
312+
export function mergeWithDefaultGridLineConfig(config: GridLineConfig): GridLineConfig {
313+
return {
314+
stroke: config.stroke || DEFAULT_GRID_LINE_CONFIG.stroke,
315+
strokeWidth: config.strokeWidth || DEFAULT_GRID_LINE_CONFIG.strokeWidth,
316+
opacity: config.opacity || DEFAULT_GRID_LINE_CONFIG.opacity,
317+
dash: config.dash || DEFAULT_GRID_LINE_CONFIG.dash,
318+
};
293319
}
294320

295321
export function getMinMaxRange(

src/lib/series/specs.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { GridLineConfig } from '../themes/theme';
12
import { Accessor } from '../utils/accessor';
23
import { Domain } from '../utils/domain';
34
import { AxisId, GroupId, SpecId } from '../utils/ids';
@@ -101,6 +102,8 @@ export type AreaSeriesSpec = BasicSeriesSpec & {
101102
export interface AxisSpec {
102103
/** The ID of the spec, generated via getSpecId method */
103104
id: AxisId;
105+
/** Style options for grid line */
106+
gridLineStyle?: GridLineConfig;
104107
/** The ID of the axis group, generated via getGroupId method
105108
* @default __global__
106109
*/

0 commit comments

Comments
 (0)