Skip to content

Commit 2b207b6

Browse files
MisterMurzibs
andauthored
bugfix: resolve Y values being incorrectly transformed when xAxis LabelRotation Applied (#504)
Co-authored-by: Eli Zibin <1131641+zibs@users.noreply.github.com> Co-authored-by: Eli Zibin <elizibin@gmail.com>
1 parent 068a346 commit 2b207b6

File tree

2 files changed

+77
-77
lines changed

2 files changed

+77
-77
lines changed

.changeset/forty-poets-sin.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"victory-native": patch
3+
---
4+
5+
fix rotate label transformation

lib/src/cartesian/utils/transformInputData.ts

Lines changed: 72 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,63 @@ export const transformInputData = <
9191
{} as TransformedData<RawData, XK, YK>["y"],
9292
);
9393

94+
const rawChartWidth = outputWindow.xMax - outputWindow.xMin;
95+
const xTickValues = xAxis?.tickValues;
96+
const xTicks = xAxis?.tickCount;
97+
98+
const tickDomainsX = getDomainFromTicks(xTickValues);
99+
const ix = data.map((datum) => datum[xKey]) as InputFields<RawData>[XK][];
100+
const ixNum = ix.map((val, i) => (isNumericalData ? (val as number) : i));
101+
102+
// For non‐numeric (ordinal) data, use the index values
103+
// if user provides a domain- use that as our min/max
104+
// if tickValues are provided- we use that instead
105+
// if we find min/max of y values across all yKeys- and use that for yrange instead
106+
const ixMin = isNumericalData
107+
? asNumber(domain?.x?.[0] ?? tickDomainsX?.[0] ?? ixNum.at(0))
108+
: 0;
109+
const ixMax = isNumericalData
110+
? asNumber(domain?.x?.[1] ?? tickDomainsX?.[1] ?? ixNum.at(-1))
111+
: ixNum.length - 1;
112+
113+
const xTempScale = makeScale({
114+
inputBounds: ixMin === ixMax ? [ixMin - 1, ixMax + 1] : [ixMin, ixMax],
115+
outputBounds: [0, rawChartWidth],
116+
});
117+
118+
// normalize xTicks values either via the d3 scaleLinear ticks() function or our custom downSample function
119+
// 4consistency we do it here- so we have both y and x ticks to pass to the axis generator
120+
const xTicksNormalized = xTickValues
121+
? downsampleTicks(xTickValues, xTicks)
122+
: xTempScale.ticks(xTicks);
123+
124+
const maxXLabel = Math.max(
125+
...xTicksNormalized.map((xTick) => {
126+
const labelValue = xAxis.formatXLabel
127+
? xAxis.formatXLabel(
128+
xTick as unknown as Parameters<typeof xAxis.formatXLabel>[0],
129+
)
130+
: String(xTick);
131+
const labelStr = String(labelValue);
132+
if (!xAxis.font) return 0;
133+
const glyphIDs = xAxis.font.getGlyphIDs(labelStr);
134+
const widths = xAxis.font.getGlyphWidths?.(glyphIDs) ?? [];
135+
return widths.reduce((sum, w) => sum + w, 0);
136+
}),
137+
);
138+
139+
// workt with adjustedoutputwindow isntead of directly
140+
// working with outpuwidnow
141+
const adjustedOutputWindow = { ...outputWindow };
142+
143+
if (labelRotate && xAxis.labelPosition === "outset") {
144+
const rotateOffset = Math.abs(maxXLabel * getOffsetFromAngle(labelRotate));
145+
if (xAxis.axisSide === "bottom") {
146+
adjustedOutputWindow.yMax -= rotateOffset;
147+
} else if (xAxis.axisSide === "top") {
148+
adjustedOutputWindow.yMin += rotateOffset;
149+
}
150+
}
94151
// 1. Set up our y axes first...
95152
// Transform data for each y-axis configuration
96153
const yAxesTransformed = (yAxes ?? [{}])?.map((yAxis) => {
@@ -141,24 +198,22 @@ export const transformInputData = <
141198
const xAxisSide = xAxis?.axisSide;
142199
const xLabelPosition = xAxis?.labelPosition;
143200

144-
// bottom, outset
145201
if (xAxisSide === "bottom" && xLabelPosition === "outset") {
146202
return [
147-
outputWindow.yMin,
148-
outputWindow.yMax +
203+
adjustedOutputWindow.yMin,
204+
adjustedOutputWindow.yMax +
149205
(xTickCount > 0 ? -fontHeight - yLabelOffset * 2 : 0),
150206
];
151207
}
152-
// Top outset
153208
if (xAxisSide === "top" && xLabelPosition === "outset") {
154209
return [
155-
outputWindow.yMin +
210+
adjustedOutputWindow.yMin +
156211
(xTickCount > 0 ? fontHeight + yLabelOffset * 2 : 0),
157-
outputWindow.yMax,
212+
adjustedOutputWindow.yMax,
158213
];
159214
}
160-
// Inset labels don't need added offsets
161-
return [outputWindow.yMin, outputWindow.yMax];
215+
216+
return [adjustedOutputWindow.yMin, adjustedOutputWindow.yMax];
162217
})();
163218

164219
const yScale = makeScale({
@@ -244,6 +299,7 @@ export const transformInputData = <
244299
const labelWidth = yAxesTransformed[index]?.maxYLabel ?? 0;
245300

246301
// Adjust xMin or xMax based on the axis side and label position
302+
// make ajdustments for label rotation here
247303
if (yAxisSide === "left" && yLabelPosition === "outset") {
248304
xMinAdjustment += yTickCount > 0 ? labelWidth + yLabelOffset : 0;
249305
} else if (yAxisSide === "right" && yLabelPosition === "outset") {
@@ -253,33 +309,11 @@ export const transformInputData = <
253309

254310
// Return the adjusted output range
255311
return [
256-
outputWindow.xMin + xMinAdjustment,
257-
outputWindow.xMax + xMaxAdjustment,
312+
adjustedOutputWindow.xMin + xMinAdjustment,
313+
adjustedOutputWindow.xMax + xMaxAdjustment,
258314
];
259315
})();
260316

261-
const xTickValues = xAxis?.tickValues;
262-
263-
// The user can specify either:
264-
// custom X tick values
265-
266-
// OR
267-
// custom X tick count
268-
const xTicks = xAxis?.tickCount;
269-
// x tick domain of [number, number]
270-
const tickDomainsX = getDomainFromTicks(xTickValues);
271-
272-
// Input x is just extracting the xKey from each datum
273-
const ix = data.map((datum) => datum[xKey]) as InputFields<RawData>[XK][];
274-
const ixNum = ix.map((val, i) => (isNumericalData ? (val as number) : i));
275-
276-
// Generate our x-scale
277-
// If user provides a domain, use that as our min / max
278-
// Else if, tickValues are provided, we use that instead
279-
// Else, we find min / max of y values across all yKeys, and use that for y range instead.
280-
const ixMin = asNumber(domain?.x?.[0] ?? tickDomainsX?.[0] ?? ixNum.at(0)),
281-
ixMax = asNumber(domain?.x?.[1] ?? tickDomainsX?.[1] ?? ixNum.at(-1));
282-
283317
const xInputBounds: [number, number] =
284318
ixMin === ixMax ? [ixMin - 1, ixMax + 1] : [ixMin, ixMax];
285319
const xScale = makeScale({
@@ -295,50 +329,11 @@ export const transformInputData = <
295329

296330
// Normalize xTicks values either via the d3 scaleLinear ticks() function or our custom downSample function
297331
// For consistency we do it here, so we have both y and x ticks to pass to the axis generator
298-
const xTicksNormalized = xTickValues
299-
? downsampleTicks(xTickValues, xTicks)
300-
: xScale.ticks(xTicks);
301-
302-
// If labelRotate is true, dynamically adjust yScale range to accommodate the maximum X label width
303-
if (labelRotate) {
304-
const maxXLabel = Math.max(
305-
...xTicksNormalized.map(
306-
(xTick) =>
307-
xAxis?.font
308-
?.getGlyphWidths?.(
309-
xAxis.font.getGlyphIDs(
310-
xAxis?.formatXLabel?.(xTick as never) || String(xTick),
311-
),
312-
)
313-
.reduce((sum, value) => sum + value, 0) ?? 0,
314-
),
315-
);
316-
317-
// First, we pass labelRotate as radian to Math.sin to get labelOffset multiplier based on maxLabel width
318-
// We then use this multiplier to calculate labelOffset for rotated labels
319-
const rotateLabelOffset = Math.abs(
320-
maxXLabel * getOffsetFromAngle(labelRotate),
321-
);
322-
323-
const yScaleRange0 = yAxesTransformed[0]?.yScale.range().at(0) as number;
324-
const yScaleRange1 = yAxesTransformed[0]?.yScale.range().at(-1) as number;
325-
326-
// bottom, outset
327-
if (xAxis?.axisSide === "bottom" && xAxis?.labelPosition === "outset") {
328-
yAxesTransformed[0]?.yScale.range([
329-
yScaleRange0,
330-
yScaleRange1 - rotateLabelOffset,
331-
]);
332-
}
333-
334-
// top, outset
335-
if (xAxis?.axisSide === "top" && xAxis?.labelPosition === "outset") {
336-
yAxesTransformed[0]?.yScale.range([
337-
yScaleRange0 + rotateLabelOffset,
338-
yScaleRange1,
339-
]);
340-
}
341-
}
332+
const finalXTicksNormalized = isNumericalData
333+
? xTickValues
334+
? downsampleTicks(xTickValues, xTicks)
335+
: xScale.ticks(xTicks)
336+
: ixNum;
342337

343338
const ox = ixNum.map((x) => xScale(x)!);
344339

@@ -348,7 +343,7 @@ export const transformInputData = <
348343
isNumericalData,
349344
ox,
350345
xScale,
351-
xTicksNormalized,
346+
xTicksNormalized: finalXTicksNormalized,
352347
// conform to type NonEmptyArray<T>
353348
yAxes: [yAxesTransformed[0]!, ...yAxesTransformed.slice(1)],
354349
};

0 commit comments

Comments
 (0)