Skip to content

Commit d0b5781

Browse files
committedMar 27, 2021
allow automated axis padding for "gap" and "no-gap"
1 parent 78d4f39 commit d0b5781

File tree

4 files changed

+103
-77
lines changed

4 files changed

+103
-77
lines changed
 

‎src/cartesian/XAxis.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ interface XAxisProps extends BaseAxisProps {
2020
* Ticks must be numbers when the axis is the type of number
2121
*/
2222
ticks?: (string | number)[];
23-
padding?: { left?: number; right?: number };
23+
padding?: { left?: number; right?: number } | 'gap' | 'no-gap';
2424
minTickGap?: number;
2525
interval?: AxisInterval;
2626
reversed?: boolean;

‎src/chart/generateCategoricalChart.tsx

+5-3
Original file line numberDiff line numberDiff line change
@@ -807,12 +807,12 @@ export const generateCategoricalChart = ({
807807
}));
808808
}
809809
}
810-
const componsedFn = item && item.type && item.type.getComposedData;
810+
const composedFn = item && item.type && item.type.getComposedData;
811811

812-
if (componsedFn) {
812+
if (composedFn) {
813813
formatedItems.push({
814814
props: {
815-
...componsedFn({
815+
...composedFn({
816816
...axisObj,
817817
displayedData,
818818
props,
@@ -1370,6 +1370,8 @@ export const generateCategoricalChart = ({
13701370
/**
13711371
* The handler of mouse entering a scatter
13721372
* @param {Object} el The active scatter
1373+
* @param {Number} index the index of the item
1374+
* @param {Object} e the click event
13731375
* @return {Object} no return
13741376
*/
13751377
handleItemMouseEnter = (el: any) => {

‎src/util/CartesianUtils.ts

+36-3
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import _ from 'lodash';
22
import { getTicksOfScale, parseScale, checkDomainOfScale, getBandSizeOfAxis } from './ChartUtils';
33
import { findChildByType } from './ReactUtils';
44
import { Coordinate, AxisType } from './types';
5+
import { getPercentValue } from './DataUtils';
56

67
/**
78
* Calculate the scale function, position, width, height of axes
@@ -32,15 +33,47 @@ export const formatAxisMap = (props: any, axisMap: any, offset: any, axisType: A
3233
const { orientation, domain, padding = {}, mirror, reversed } = axis;
3334
const offsetKey = `${orientation}${mirror ? 'Mirror' : ''}`;
3435

35-
let range, x, y, needSpace;
36+
let calculatedPadding, range, x, y, needSpace;
37+
38+
if (axis.type === 'number' && (axis.padding === 'gap' || axis.padding === 'no-gap')) {
39+
const diff = domain[1] - domain[0];
40+
let smallestDistanceBetweenValues = Infinity;
41+
const sortedValues = axis.categoricalDomain.sort();
42+
sortedValues.forEach((value: number, index: number) => {
43+
if (index > 0) {
44+
smallestDistanceBetweenValues = Math.min(
45+
(value || 0) - (sortedValues[index - 1] || 0),
46+
smallestDistanceBetweenValues,
47+
);
48+
}
49+
});
50+
const smallestDistanceInPercent = smallestDistanceBetweenValues / diff;
51+
const rangeWidth = axis.layout === 'vertical' ? offset.height : offset.width;
52+
53+
if (axis.padding === 'gap') {
54+
calculatedPadding = (smallestDistanceInPercent * rangeWidth) / 2;
55+
}
56+
57+
if (axis.padding === 'no-gap') {
58+
const gap = getPercentValue(props.barCategoryGap, smallestDistanceInPercent * rangeWidth);
59+
const halfBand = (smallestDistanceInPercent * rangeWidth) / 2;
60+
calculatedPadding = halfBand - gap - ((halfBand - gap) / rangeWidth) * gap;
61+
}
62+
}
3663

3764
if (axisType === 'xAxis') {
38-
range = [offset.left + (padding.left || 0), offset.left + offset.width - (padding.right || 0)];
65+
range = [
66+
offset.left + (padding.left || 0) + (calculatedPadding || 0),
67+
offset.left + offset.width - (padding.right || 0) - (calculatedPadding || 0),
68+
];
3969
} else if (axisType === 'yAxis') {
4070
range =
4171
layout === 'horizontal'
4272
? [offset.top + offset.height - (padding.bottom || 0), offset.top + (padding.top || 0)]
43-
: [offset.top + (padding.top || 0), offset.top + offset.height - (padding.bottom || 0)];
73+
: [
74+
offset.top + (padding.top || 0) + (calculatedPadding || 0),
75+
offset.top + offset.height - (padding.bottom || 0) - (calculatedPadding || 0),
76+
];
4477
} else {
4578
({ range } = axis);
4679
}

‎test/specs/cartesian/XAxisSpec.js

+61-70
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import React from 'react';
22
import { expect } from 'chai';
33
import { Surface, ScatterChart, Scatter, LineChart, Line, XAxis, YAxis, CartesianAxis } from 'recharts';
44
import { mount, render } from 'enzyme';
5+
import { Bar, BarChart } from '../../../src';
56

67
describe('<XAxis />', () => {
78
const data = [
@@ -21,35 +22,34 @@ describe('<XAxis />', () => {
2122
{ name: 'Page F', uv: 189, pv: 4800, amt: 2400 },
2223
];
2324

24-
2525
it('Render 1 x-CartesianAxis and 1 y-CartesianAxis ticks in ScatterChart', () => {
2626
const wrapper = mount(
2727
<ScatterChart width={400} height={400} margin={{ top: 20, right: 20, bottom: 20, left: 20 }}>
28-
<XAxis dataKey={'x'} name="stature" unit="cm" />
29-
<YAxis dataKey={'y'} name="weight" unit="kg" />
28+
<XAxis dataKey="x" name="stature" unit="cm" />
29+
<YAxis dataKey="y" name="weight" unit="kg" />
3030
<Scatter name="A school" data={data} fill="#ff7300" />
31-
</ScatterChart>
31+
</ScatterChart>,
3232
);
3333
expect(wrapper.find(CartesianAxis).length).to.equal(2);
3434
});
3535

36-
it('Don\'t render anything', () => {
36+
it("Don't render anything", () => {
3737
const wrapper = render(
3838
<Surface width={500} height={500}>
39-
<XAxis dataKey={'x'} name="stature" unit="cm" />
40-
</Surface>
39+
<XAxis dataKey="x" name="stature" unit="cm" />
40+
</Surface>,
4141
);
4242

4343
expect(wrapper.find('svg').children.length).to.equal(1);
4444
expect(wrapper.find('svg noscript').children.length).to.equal(1);
4545
});
4646

47-
it('Don\'t render x-axis when hide is setted to be true', () => {
47+
it("Don't render x-axis when hide is setted to be true", () => {
4848
const wrapper = render(
4949
<LineChart width={400} height={400} data={lineData} margin={{ top: 20, right: 20, bottom: 20, left: 20 }}>
5050
<XAxis hide />
5151
<Line type="monotone" dataKey="uv" stroke="#ff7300" />
52-
</LineChart>
52+
</LineChart>,
5353
);
5454
expect(wrapper.find('.recharts-x-axis').length).to.equal(0);
5555
});
@@ -59,7 +59,7 @@ describe('<XAxis />', () => {
5959
<LineChart width={400} height={400} data={lineData} margin={{ top: 20, right: 20, bottom: 20, left: 20 }}>
6060
<XAxis ticks={[0, 4]} />
6161
<Line type="monotone" dataKey="uv" stroke="#ff7300" />
62-
</LineChart>
62+
</LineChart>,
6363
);
6464
expect(wrapper.find('.xAxis .recharts-cartesian-axis-tick').length).to.equal(2);
6565
});
@@ -69,107 +69,98 @@ describe('<XAxis />', () => {
6969
<LineChart width={400} height={400} data={lineData} margin={{ top: 20, right: 20, bottom: 20, left: 20 }}>
7070
<XAxis dataKey="name" tickFormatter={(value, i) => `${i}`} />
7171
<Line type="monotone" dataKey="uv" stroke="#ff7300" />
72-
</LineChart>
72+
</LineChart>,
7373
);
7474
expect(wrapper.find('.xAxis .recharts-cartesian-axis-tick').first()).text().to.equal('0');
7575
});
7676

7777
it('Render duplicated ticks of XAxis', () => {
7878
const lineData = [
79-
{ name: "03/07/2017", balance: 23126.11 },
80-
{ name: "03/02/2017", balance: 23137.39 },
81-
{ name: "03/01/2017", balance: 24609.55 },
82-
{ name: "03/01/2017", balance: 26827.66 },
83-
{ name: "02/24/2017", balance: 26807.66 },
84-
{ name: "02/21/2017", balance: 23835.62 },
85-
{ name: "02/16/2017", balance: 23829.62 }
79+
{ name: '03/07/2017', balance: 23126.11 },
80+
{ name: '03/02/2017', balance: 23137.39 },
81+
{ name: '03/01/2017', balance: 24609.55 },
82+
{ name: '03/01/2017', balance: 26827.66 },
83+
{ name: '02/24/2017', balance: 26807.66 },
84+
{ name: '02/21/2017', balance: 23835.62 },
85+
{ name: '02/16/2017', balance: 23829.62 },
8686
];
8787

8888
const wrapper = render(
89-
<LineChart
90-
width={600}
91-
height={300}
92-
data={lineData}
93-
margin={{ top: 5, right: 30, left: 20, bottom: 5 }}
94-
>
89+
<LineChart width={600} height={300} data={lineData} margin={{ top: 5, right: 30, left: 20, bottom: 5 }}>
9590
<XAxis dataKey="name" interval={0} />
9691
<YAxis />
9792
<Line type="monotone" dataKey="balance" stroke="#8884d8" activeDot={{ r: 8 }} />
98-
</LineChart>
93+
</LineChart>,
9994
);
10095
expect(wrapper.find('.recharts-xAxis .recharts-cartesian-axis-tick').length).to.equal(lineData.length);
10196
});
10297

10398
it('Render ticks of when the scale of XAxis is time', () => {
10499
const timeData = [
105100
{
106-
x: new Date("2019-07-04T00:00:00.000Z"),
107-
y: 5
101+
x: new Date('2019-07-04T00:00:00.000Z'),
102+
y: 5,
108103
},
109104
{
110-
x: new Date("2019-07-05T00:00:00.000Z"),
111-
y: 30
105+
x: new Date('2019-07-05T00:00:00.000Z'),
106+
y: 30,
112107
},
113108
{
114-
x: new Date("2019-07-06T00:00:00.000Z"),
115-
y: 50
109+
x: new Date('2019-07-06T00:00:00.000Z'),
110+
y: 50,
116111
},
117112
{
118-
x: new Date("2019-07-07T00:00:00.000Z"),
119-
y: 43
113+
x: new Date('2019-07-07T00:00:00.000Z'),
114+
y: 43,
120115
},
121116
{
122-
x: new Date("2019-07-08T00:00:00.000Z"),
123-
y: 20
117+
x: new Date('2019-07-08T00:00:00.000Z'),
118+
y: 20,
124119
},
125120
{
126-
x: new Date("2019-07-09T00:00:00.000Z"),
127-
y: -20
121+
x: new Date('2019-07-09T00:00:00.000Z'),
122+
y: -20,
128123
},
129124
{
130-
x: new Date("2019-07-10T00:00:00.000Z"),
131-
y: 30
132-
}
125+
x: new Date('2019-07-10T00:00:00.000Z'),
126+
y: 30,
127+
},
133128
];
134129

135130
const wrapper = render(
136-
<LineChart
137-
width={600}
138-
height={300}
139-
data={timeData}
140-
margin={{ top: 5, right: 30, left: 20, bottom: 5 }}
141-
>
142-
<XAxis dataKey="x" domain={[timeData[0].x.getTime(), timeData[timeData.length - 1].x.getTime()]} scale="time" type="number" />
131+
<LineChart width={600} height={300} data={timeData} margin={{ top: 5, right: 30, left: 20, bottom: 5 }}>
132+
<XAxis
133+
dataKey="x"
134+
domain={[timeData[0].x.getTime(), timeData[timeData.length - 1].x.getTime()]}
135+
scale="time"
136+
type="number"
137+
/>
143138
<YAxis />
144139
<Line type="monotone" dataKey="y" stroke="#8884d8" activeDot={{ r: 8 }} />
145-
</LineChart>
140+
</LineChart>,
146141
);
147142
expect(wrapper.find('.recharts-xAxis .recharts-cartesian-axis-tick').length).to.equal(1);
148143
});
149144

150-
it('Render duplicated ticks of XAxis', () => {
151-
const lineData = [
152-
{ name: "03/07/2017", balance: 23126.11 },
153-
{ name: "03/02/2017", balance: 23137.39 },
154-
{ name: "03/01/2017", balance: 24609.55 },
155-
{ name: "03/01/2017", balance: 26827.66 },
156-
{ name: "02/24/2017", balance: 26807.66 },
157-
{ name: "02/21/2017", balance: 23835.62 },
158-
{ name: "02/16/2017", balance: 23829.62 }
159-
];
145+
it('Render Bars with gap', () => {
146+
const wrapper = mount(
147+
<BarChart width={300} height={300} data={data}>
148+
<Bar dataKey="y" />
149+
<XAxis dataKey="x" type="number" domain={['dataMin', 'dataMax']} padding="gap" />
150+
<YAxis dataKey="y" />
151+
</BarChart>,
152+
);
153+
expect(parseInt(wrapper.find(Bar).prop('data')[0].x, 10)).to.equal(70);
154+
});
160155

161-
const wrapper = render(
162-
<LineChart
163-
width={600}
164-
height={300}
165-
data={lineData}
166-
margin={{ top: 5, right: 30, left: 20, bottom: 5 }}
167-
>
168-
<XAxis dataKey="name" scale="time" interval={0} />
169-
<YAxis />
170-
<Line type="monotone" dataKey="balance" stroke="#8884d8" activeDot={{ r: 8 }} />
171-
</LineChart>
156+
it('Render Bars with no gap', () => {
157+
const wrapper = mount(
158+
<BarChart width={300} height={300} data={data}>
159+
<Bar dataKey="y" />
160+
<XAxis dataKey="x" type="number" domain={['dataMin', 'dataMax']} padding="no-gap" />
161+
<YAxis dataKey="y" />
162+
</BarChart>,
172163
);
173-
expect(wrapper.find('.recharts-xAxis .recharts-cartesian-axis-tick').length).to.equal(lineData.length);
164+
expect(parseInt(wrapper.find(Bar).prop('data')[0].x, 10)).to.equal(66);
174165
});
175166
});

0 commit comments

Comments
 (0)
Please sign in to comment.