Skip to content

Commit efbe0b1

Browse files
committed
fix: restore legacy LineChart point spacing
1 parent 235eb0d commit efbe0b1

13 files changed

Lines changed: 259 additions & 75 deletions

File tree

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
# Changelog
22

3+
## v7.0.2
4+
5+
- restore v6 point-slot spacing for the legacy root `LineChart` so custom
6+
overlays remain aligned after upgrading to v7
7+
- clarify that edge-to-edge layout belongs to modern `/v2` charts and document
8+
the legacy point-coordinate callbacks
9+
310
## v7.0.1
411

512
- refresh the npm README and package description with clearer chart coverage

docs/charts/legacy.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,10 @@ const chartConfig = {
4949

5050
::chart-preview{id="legacy-line"}
5151

52+
The legacy root `LineChart` preserves v6 point-slot spacing for existing
53+
overlays. For custom point content, prefer `renderDotContent`, which supplies
54+
the rendered `x` and `y` coordinates, instead of recalculating positions.
55+
5256
## BarChart
5357

5458
```tsx

docs/migration/from-v1.md

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,10 @@ import { LineChart } from "react-native-chart-kit";
3838
/>;
3939
```
4040

41-
The compatibility promise covers the common public API, not undocumented internals, exact SVG node order, or layout bugs that made labels clip.
41+
The compatibility promise covers the common public API and the legacy
42+
`LineChart` point spacing used by public render and callback props. It does not
43+
cover undocumented internals, exact SVG node order, or layout bugs that made
44+
labels clip.
4245

4346
## Modern API
4447

@@ -76,7 +79,13 @@ v2 defaults prioritize correct mobile layout over pixel-perfect legacy spacing:
7679
- Scrollable charts use visible data windows instead of requiring manual SVG width hacks.
7780
- `null` values create line gaps by default in modern charts.
7881

79-
Keep migrated charts on the legacy package path when they rely on old spacing during a transition. Do not use legacy spacing patterns for new charts.
82+
These layout differences apply to the modern `/v2` charts. The legacy root
83+
`LineChart` keeps the v6 point-slot spacing so existing overlays remain aligned.
84+
When adding custom content, prefer the `x` and `y` coordinates supplied by
85+
`renderDotContent` and `onDataPointClick` instead of duplicating chart geometry.
86+
87+
Keep migrated charts on the legacy package path when they rely on old spacing
88+
during a transition. Do not use legacy spacing patterns for new charts.
8089

8190
## Package Paths
8291

docs/migration/prop-mapping.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,10 @@ Do not depend on:
108108
- exact SVG child order
109109
- old clipping and padding bugs
110110
- deep imports from implementation files
111-
- chart-specific hacks that depend on old pixel positions
112111

113-
If a legacy chart needs one of those behaviors, keep it on the compatibility surface until there is an explicit modern extension point.
112+
The legacy root `LineChart` preserves v6 point-slot spacing for existing
113+
overlays. Prefer the `x` and `y` coordinates supplied by `renderDotContent` and
114+
`onDataPointClick` when adding or updating custom point content.
115+
116+
If a legacy chart needs an unsupported behavior, keep it on the compatibility
117+
surface until there is an explicit modern extension point.

docs/troubleshooting.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,9 @@ checkout of this repository.
7575

7676
## Compatibility Chart Looks Different From v1
7777

78-
v2 compatibility preserves common props and data shapes, not pixel-perfect old bugs. Differences are expected when old behavior clipped labels, depended on SVG node order, or used undocumented internals.
78+
The legacy root import preserves common props, data shapes, and v6 `LineChart`
79+
point-slot spacing. It does not preserve old label-clipping bugs, exact SVG node
80+
order, or undocumented internals. The modern `/v2` charts intentionally use
81+
their own layout behavior.
7982

8083
Use [the v1 migration guide](migration/from-v1.md) and [prop mapping](migration/prop-mapping.md) to decide whether the chart should stay on the compatibility surface or move to the modern API.

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "react-native-chart-kit",
3-
"version": "7.0.1",
3+
"version": "7.0.2",
44
"description": "Beautiful React Native charts for dashboards, reports, and data-rich mobile apps.",
55
"keywords": [
66
"react-native",
@@ -104,7 +104,7 @@
104104
"test:unit": "vitest run --exclude \"packages/**/test/compat/**/*.test.ts\"",
105105
"test:coverage": "vitest run --coverage",
106106
"test:compat": "vitest run packages/core/test/compat",
107-
"typecheck": "tsc --noEmit && npm run core:typecheck && npm run svg:typecheck && npm run rn:typecheck",
107+
"typecheck": "tsc --noEmit && tsc -p tsconfig.test.json --noEmit && npm run core:typecheck && npm run svg:typecheck && npm run rn:typecheck",
108108
"core:typecheck": "tsc -p packages/core/tsconfig.json --noEmit && tsc -p packages/core/tsconfig.test.json --noEmit",
109109
"core:build": "tsc -p packages/core/tsconfig.json",
110110
"svg:typecheck": "tsc -p packages/svg-renderer/tsconfig.json --noEmit && tsc -p packages/svg-renderer/tsconfig.test.json --noEmit",

src/charts/line/LineChart.tsx

Lines changed: 48 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -23,23 +23,11 @@ import AbstractChart, {
2323
} from "../../shared/AbstractChart";
2424
import { ChartData, Dataset } from "../../shared/types";
2525
import { getNumberProp } from "../../shared/utils";
26+
import { getLegacyLineChartPointX, getLegacyLineChartXMax } from "./geometry";
2627
import { LegendItem } from "./LegendItem";
2728

2829
let AnimatedCircle = Animated.createAnimatedComponent(Circle);
2930

30-
const getFiniteNumber = (value: unknown, fallback: number) => {
31-
if (typeof value === "number") {
32-
return Number.isFinite(value) ? value : fallback;
33-
}
34-
35-
if (typeof value === "string") {
36-
const parsed = Number.parseFloat(value);
37-
return Number.isFinite(parsed) ? parsed : fallback;
38-
}
39-
40-
return fallback;
41-
};
42-
4331
export interface LineChartData extends ChartData {
4432
legend?: string[];
4533
}
@@ -249,22 +237,6 @@ class LineChart extends AbstractChart<LineChartProps, LineChartState> {
249237
return dataset.strokeWidth || this.props.chartConfig.strokeWidth || 3;
250238
};
251239

252-
getChartRightPadding = (data: Dataset[], withDots: boolean) => {
253-
const propsForDots = this.props.chartConfig?.propsForDots ?? {};
254-
const dotRadius = withDots ? getFiniteNumber(propsForDots.r, 4) : 0;
255-
const dotStrokeWidth = withDots
256-
? getFiniteNumber(propsForDots.strokeWidth, 0)
257-
: 0;
258-
const maxStrokeWidth = data.reduce(
259-
(maxWidth, dataset) => Math.max(maxWidth, this.getStrokeWidth(dataset)),
260-
0
261-
);
262-
263-
return Math.ceil(
264-
Math.max(4, dotRadius + dotStrokeWidth / 2, maxStrokeWidth / 2)
265-
);
266-
};
267-
268240
getDatas = (data: Dataset[]): number[] => {
269241
return data.reduce(
270242
(acc, item) =>
@@ -326,7 +298,12 @@ class LineChart extends AbstractChart<LineChartProps, LineChartState> {
326298
return;
327299
}
328300

329-
const cx = paddingRight + (i * (width - paddingRight)) / xMax;
301+
const cx = getLegacyLineChartPointX({
302+
index: i,
303+
width,
304+
paddingRight,
305+
xMax
306+
});
330307

331308
const cy =
332309
((baseHeight - this.calcHeight(x, datas, height)) / 4) * 3 +
@@ -497,10 +474,12 @@ class LineChart extends AbstractChart<LineChartProps, LineChartState> {
497474
3 +
498475
paddingTop;
499476
yValues.push(yval);
500-
const xval =
501-
paddingRight +
502-
((dataset.data.length - index - 1) * (width - paddingRight)) /
503-
dataset.data.length;
477+
const xval = getLegacyLineChartPointX({
478+
index: dataset.data.length - index - 1,
479+
width,
480+
paddingRight,
481+
xMax: dataset.data.length
482+
});
504483
xValues.push(xval);
505484

506485
yValuesLabel.push(
@@ -610,7 +589,12 @@ class LineChart extends AbstractChart<LineChartProps, LineChartState> {
610589
points={
611590
dataset.data
612591
.map((d, i) => {
613-
const x = paddingRight + (i * (width - paddingRight)) / xMax;
592+
const x = getLegacyLineChartPointX({
593+
index: i,
594+
width,
595+
paddingRight,
596+
xMax
597+
});
614598

615599
const y =
616600
((baseHeight - this.calcHeight(d, datas, height)) / 4) * 3 +
@@ -619,10 +603,12 @@ class LineChart extends AbstractChart<LineChartProps, LineChartState> {
619603
return `${x},${y}`;
620604
})
621605
.join(" ") +
622-
` ${
623-
paddingRight +
624-
((width - paddingRight) / xMax) * (dataset.data.length - 1)
625-
},${
606+
` ${getLegacyLineChartPointX({
607+
index: dataset.data.length - 1,
608+
width,
609+
paddingRight,
610+
xMax
611+
})},${
626612
(height / 4) * 3 + paddingTop
627613
} ${paddingRight},${(height / 4) * 3 + paddingTop}`
628614
}
@@ -665,7 +651,12 @@ class LineChart extends AbstractChart<LineChartProps, LineChartState> {
665651
let lastPoint: string;
666652
const points = dataset.data.map((d, i) => {
667653
if (d === null) return lastPoint;
668-
const x = (i * (width - paddingRight)) / xMax + paddingRight;
654+
const x = getLegacyLineChartPointX({
655+
index: i,
656+
width,
657+
paddingRight,
658+
xMax
659+
});
669660
const y =
670661
((baseHeight - this.calcHeight(d, datas, height)) / 4) * 3 +
671662
paddingTop;
@@ -691,10 +682,7 @@ class LineChart extends AbstractChart<LineChartProps, LineChartState> {
691682
};
692683

693684
getXMaxValues = (data: Dataset[]) => {
694-
return data.reduce((acc, cur) => {
695-
const xMax = Math.max(cur.data.length - 1, 1);
696-
return xMax > acc ? xMax : acc;
697-
}, 1);
685+
return getLegacyLineChartXMax(data);
698686
};
699687

700688
getBezierLinePoints = (
@@ -718,7 +706,14 @@ class LineChart extends AbstractChart<LineChartProps, LineChartState> {
718706
const xMax = this.getXMaxValues(data);
719707

720708
const x = (i: number) =>
721-
Math.floor(paddingRight + (i * (width - paddingRight)) / xMax);
709+
Math.floor(
710+
getLegacyLineChartPointX({
711+
index: i,
712+
width,
713+
paddingRight,
714+
xMax
715+
})
716+
);
722717

723718
const baseHeight = this.calcBaseHeight(datas, height);
724719

@@ -800,10 +795,12 @@ class LineChart extends AbstractChart<LineChartProps, LineChartState> {
800795
paddingTop,
801796
data
802797
}) +
803-
` L${
804-
paddingRight +
805-
((width - paddingRight) / xMax) * (dataset.data.length - 1)
806-
},${
798+
` L${getLegacyLineChartPointX({
799+
index: dataset.data.length - 1,
800+
width,
801+
paddingRight,
802+
xMax
803+
})},${
807804
(height / 4) * 3 + paddingTop
808805
} L${paddingRight},${(height / 4) * 3 + paddingTop} Z`;
809806

@@ -874,17 +871,8 @@ class LineChart extends AbstractChart<LineChartProps, LineChartState> {
874871
paddingBottom = 0
875872
} = style;
876873

877-
const chartRightPadding = this.getChartRightPadding(
878-
data.datasets,
879-
withDots
880-
);
881-
const chartWidth = Math.max(
882-
width - chartRightPadding,
883-
getFiniteNumber(paddingRight, 64) + 1
884-
);
885-
886874
const config = {
887-
width: chartWidth,
875+
width,
888876
height,
889877
verticalLabelRotation,
890878
horizontalLabelRotation
@@ -895,9 +883,6 @@ class LineChart extends AbstractChart<LineChartProps, LineChartState> {
895883
const hasScrollableData =
896884
withScrollableDot &&
897885
data.datasets.some((dataset) => dataset.data && dataset.data.length > 0);
898-
const xAxisDataIntervalCount = this.getXMaxValues(data.datasets);
899-
const xAxisLabelIntervalCount = Math.max(labels.length - 1, 1);
900-
901886
let count = Math.min(...datas) === Math.max(...datas) ? 1 : 4;
902887
if (segments) {
903888
count = segments;
@@ -963,8 +948,7 @@ class LineChart extends AbstractChart<LineChartProps, LineChartState> {
963948
...config,
964949
data: firstDataset.data,
965950
paddingTop: paddingTop as number,
966-
paddingRight: paddingRight as number,
967-
xAxisIntervalCount: xAxisDataIntervalCount
951+
paddingRight: paddingRight as number
968952
})
969953
: withOuterLines
970954
? this.renderVerticalLine({
@@ -981,7 +965,6 @@ class LineChart extends AbstractChart<LineChartProps, LineChartState> {
981965
labels,
982966
paddingTop: paddingTop as number,
983967
paddingRight: paddingRight as number,
984-
xAxisIntervalCount: xAxisLabelIntervalCount,
985968
formatXLabel
986969
})}
987970
</G>

src/charts/line/geometry.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import type { Dataset } from "../../shared/types";
2+
3+
export const getLegacyLineChartXMax = (data: Dataset[]) =>
4+
data.reduce(
5+
(maxLength, dataset) => Math.max(maxLength, dataset.data.length),
6+
1
7+
);
8+
9+
export const getLegacyLineChartPointX = ({
10+
index,
11+
width,
12+
paddingRight,
13+
xMax
14+
}: {
15+
index: number;
16+
width: number;
17+
paddingRight: number;
18+
xMax: number;
19+
}) => paddingRight + (index * (width - paddingRight)) / Math.max(xMax, 1);

0 commit comments

Comments
 (0)