Skip to content

Commit 1fc5598

Browse files
MichelBahlzibs
andauthored
Feat: add scale logarithmic (#592)
Co-authored-by: Eli Zibin <1131641+zibs@users.noreply.github.com>
1 parent 8921055 commit 1fc5598

File tree

8 files changed

+78
-17
lines changed

8 files changed

+78
-17
lines changed

.changeset/lucky-phones-relax.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"victory-native": minor
3+
---
4+
5+
Add scale logarithmic to cartesian charts

example/app/line-chart.tsx

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {
1010
type XAxisSide,
1111
type YAxisSide,
1212
} from "victory-native";
13-
import type { AxisLabelPosition } from "lib/src/types";
13+
import type { AxisLabelPosition, AxisScaleType } from "lib/src/types";
1414
import { useDarkMode } from "react-native-dark";
1515
import { InputSlider } from "example/components/InputSlider";
1616
import { InputSegment } from "example/components/InputSegment";
@@ -44,6 +44,7 @@ export default function LineChartPage(props: { segment: string }) {
4444
strokeWidth,
4545
xAxisSide,
4646
yAxisSide,
47+
axisScales,
4748
xLabelOffset,
4849
yLabelOffset,
4950
xTickCount,
@@ -84,6 +85,7 @@ export default function LineChartPage(props: { segment: string }) {
8485
padding={chartPadding}
8586
yKeys={["sales"]}
8687
axisOptions={{
88+
axisScales,
8789
font,
8890
lineWidth: { grid: { x: 0, y: 2 }, frame: 0 },
8991
lineColor: {
@@ -287,7 +289,6 @@ export default function LineChartPage(props: { segment: string }) {
287289
value={xAxisSide}
288290
values={["top", "bottom"]}
289291
/>
290-
291292
<InputSegment<AxisLabelPosition>
292293
label="X Axis Label position"
293294
onChange={(val) =>
@@ -339,6 +340,14 @@ export default function LineChartPage(props: { segment: string }) {
339340
value={yAxisSide}
340341
values={["left", "right"]}
341342
/>
343+
<InputSegment<AxisScaleType>
344+
label="Y Axis scales"
345+
onChange={(val) =>
346+
dispatch({ type: "SET_AXIS_SCALE", payload: { yAxisScale: val } })
347+
}
348+
value={axisScales?.yAxisScale || "linear"}
349+
values={["linear", "log"]}
350+
/>
342351
<InputColor
343352
label="Y-axis Label Color"
344353
color={colors.yLabel}

example/hooks/useOptionsReducer.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { AxisLabelPosition } from "lib/src/types";
1+
import type { AxisLabelPosition, AxisScales } from "lib/src/types";
22
import type { CurveType, XAxisSide, YAxisSide } from "victory-native";
33

44
type State = {
@@ -11,6 +11,7 @@ type State = {
1111
xTickCount: number;
1212
xAxisSide: XAxisSide;
1313
yAxisSide: YAxisSide;
14+
axisScales: AxisScales;
1415
scatterRadius: number;
1516
xAxisLabelPosition: AxisLabelPosition;
1617
yAxisLabelPosition: AxisLabelPosition;
@@ -33,6 +34,7 @@ type Action =
3334
| { type: "SET_X_TICK_COUNT"; payload: number }
3435
| { type: "SET_X_AXIS_SIDE"; payload: XAxisSide }
3536
| { type: "SET_Y_AXIS_SIDE"; payload: YAxisSide }
37+
| { type: "SET_AXIS_SCALE"; payload: AxisScales }
3638
| { type: "SET_SCATTER_RADIUS"; payload: number }
3739
| { type: "SET_X_AXIS_LABEL_POSITION"; payload: AxisLabelPosition }
3840
| { type: "SET_Y_AXIS_LABEL_POSITION"; payload: AxisLabelPosition }
@@ -64,6 +66,8 @@ export const optionsReducer = (state: State, action: Action): State => {
6466
return { ...state, xAxisSide: action.payload };
6567
case "SET_Y_AXIS_SIDE":
6668
return { ...state, yAxisSide: action.payload };
69+
case "SET_AXIS_SCALE":
70+
return { ...state, axisScales: action.payload };
6771
case "SET_SCATTER_RADIUS":
6872
return { ...state, scatterRadius: action.payload };
6973
case "SET_X_AXIS_LABEL_POSITION":
@@ -101,6 +105,9 @@ export const optionsInitialState: State = {
101105
scatterRadius: 7,
102106
xAxisSide: "bottom",
103107
yAxisSide: "left",
108+
axisScales: {
109+
yAxisScale: "linear",
110+
},
104111
xAxisLabelPosition: "outset",
105112
yAxisLabelPosition: "outset",
106113
colors: {},

lib/src/cartesian/CartesianChart.tsx

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {
1010
} from "react-native-gesture-handler";
1111
import { type MutableRefObject } from "react";
1212
import { ZoomTransform } from "d3-zoom";
13-
import type { ScaleLinear } from "d3-scale";
13+
import type { ScaleLinear, ScaleLogarithmic } from "d3-scale";
1414
import isEqual from "react-fast-compare";
1515
import type {
1616
AxisProps,
@@ -91,7 +91,6 @@ type CartesianChartProps<
9191
args: CartesianChartRenderArg<RawData, YK>,
9292
) => React.ReactNode;
9393
axisOptions?: Partial<Omit<AxisProps<RawData, XK, YK>, "xScale" | "yScale">>;
94-
9594
onChartBoundsChange?: (bounds: ChartBounds) => void;
9695
onScaleChange?: (
9796
xScale: ScaleLinear<number, number>,
@@ -164,9 +163,10 @@ function CartesianChartContent<
164163
}: CartesianChartProps<RawData, XK, YK>) {
165164
const [size, setSize] = React.useState({ width: 0, height: 0 });
166165
const chartBoundsRef = React.useRef<ChartBounds | undefined>(undefined);
167-
const xScaleRef = React.useRef<ScaleLinear<number, number> | undefined>(
168-
undefined,
169-
);
166+
const xScaleRef = React.useRef<
167+
ScaleLogarithmic<number, number> | ScaleLinear<number, number> | undefined
168+
>(undefined);
169+
170170
const yScaleRef = React.useRef<ScaleLinear<number, number> | undefined>(
171171
undefined,
172172
);
@@ -239,6 +239,7 @@ function CartesianChartContent<
239239
yAxes: normalizedAxisProps.yAxes,
240240
viewport,
241241
labelRotate: normalizedAxisProps.xAxis.labelRotate,
242+
axisScales: axisOptions?.axisScales,
242243
});
243244

244245
const primaryYAxis = yAxes[0];

lib/src/cartesian/components/CartesianAxis.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,7 @@ export const CartesianAxisDefaultProps = {
251251
tickCount: 5,
252252
labelOffset: { x: 2, y: 4 },
253253
axisSide: { x: "bottom", y: "left" },
254+
axisScales: { xAxisScale: "linear", yAxisScale: "linear" },
254255
labelPosition: "outset",
255256
formatXLabel: (label: ValueOf<InputDatum>) => String(label),
256257
formatYLabel: (label: ValueOf<InputDatum>) => String(label),

lib/src/cartesian/utils/makeScale.ts

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1-
import { type ScaleLinear, scaleLinear } from "d3-scale";
1+
import {
2+
type ScaleLinear,
3+
scaleLinear,
4+
scaleLog,
5+
type ScaleLogarithmic,
6+
} from "d3-scale";
7+
import type { AxisScaleType } from "lib/src/types";
28

39
export const makeScale = ({
410
inputBounds,
@@ -7,21 +13,38 @@ export const makeScale = ({
713
padEnd,
814
viewport,
915
isNice = false,
16+
axisScale = "linear",
1017
}: {
1118
inputBounds: [number, number];
1219
outputBounds: [number, number];
1320
viewport?: [number, number];
1421
padStart?: number;
1522
padEnd?: number;
1623
isNice?: boolean;
17-
}): ScaleLinear<number, number> => {
18-
// Linear
19-
const viewScale = scaleLinear()
20-
.domain(viewport ?? inputBounds)
21-
.range(outputBounds);
22-
const scale = scaleLinear()
23-
.domain(inputBounds)
24-
.range([viewScale(inputBounds[0]), viewScale(inputBounds[1])]);
24+
axisScale?: AxisScaleType;
25+
}): ScaleLinear<number, number> | ScaleLogarithmic<number, number> => {
26+
let scale: ScaleLinear<number, number> | ScaleLogarithmic<number, number>;
27+
28+
switch (axisScale) {
29+
case "log": {
30+
const viewScale = scaleLog()
31+
.domain(viewport ?? inputBounds)
32+
.range(outputBounds);
33+
scale = scaleLog()
34+
.domain(inputBounds)
35+
.range([viewScale(inputBounds[0]), viewScale(inputBounds[1])]);
36+
break;
37+
}
38+
default: {
39+
const viewScale = scaleLinear()
40+
.domain(viewport ?? inputBounds)
41+
.range(outputBounds);
42+
scale = scaleLinear()
43+
.domain(inputBounds)
44+
.range([viewScale(inputBounds[0]), viewScale(inputBounds[1])]);
45+
break;
46+
}
47+
}
2548

2649
if (padStart || padEnd) {
2750
scale

lib/src/cartesian/utils/transformInputData.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import type {
1212
NonEmptyArray,
1313
YAxisPropsWithDefaults,
1414
XAxisPropsWithDefaults,
15+
AxisScales,
1516
} from "../../types";
1617
import { asNumber } from "../../utils/asNumber";
1718
import { makeScale } from "./makeScale";
@@ -45,6 +46,7 @@ export const transformInputData = <
4546
yAxes,
4647
viewport,
4748
labelRotate,
49+
axisScales,
4850
}: {
4951
data: RawData[];
5052
xKey: XK;
@@ -62,6 +64,7 @@ export const transformInputData = <
6264
y?: [number, number];
6365
};
6466
labelRotate?: number;
67+
axisScales?: AxisScales;
6568
}): TransformedData<RawData, XK, YK> & {
6669
xScale: ScaleLinear<number, number>;
6770
isNumericalData: boolean;
@@ -73,6 +76,7 @@ export const transformInputData = <
7376
}>;
7477
} => {
7578
const data = [..._data];
79+
const { xAxisScale = "linear", yAxisScale = "linear" } = axisScales || {};
7680

7781
// Determine if xKey data is numerical
7882
const isNumericalData = data.every(
@@ -113,6 +117,7 @@ export const transformInputData = <
113117
const xTempScale = makeScale({
114118
inputBounds: ixMin === ixMax ? [ixMin - 1, ixMax + 1] : [ixMin, ixMax],
115119
outputBounds: [0, rawChartWidth],
120+
axisScale: xAxisScale,
116121
});
117122

118123
// normalize xTicks values either via the d3 scaleLinear ticks() function or our custom downSample function
@@ -228,6 +233,7 @@ export const transformInputData = <
228233
: domainPadding?.bottom,
229234
padStart:
230235
typeof domainPadding === "number" ? domainPadding : domainPadding?.top,
236+
axisScale: yAxisScale,
231237
});
232238

233239
const yData = yKeysForAxis.reduce(
@@ -325,6 +331,7 @@ export const transformInputData = <
325331
typeof domainPadding === "number" ? domainPadding : domainPadding?.left,
326332
padEnd:
327333
typeof domainPadding === "number" ? domainPadding : domainPadding?.right,
334+
axisScale: xAxisScale,
328335
});
329336

330337
// Normalize xTicks values either via the d3 scaleLinear ticks() function or our custom downSample function

lib/src/types.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,13 @@ export type XAxisSide = "top" | "bottom";
3535
export type YAxisSide = "left" | "right";
3636
export type AxisLabelPosition = "inset" | "outset";
3737

38+
export type AxisScaleType = "linear" | "log";
39+
40+
export type AxisScales = {
41+
xAxisScale?: AxisScaleType;
42+
yAxisScale?: AxisScaleType;
43+
};
44+
3845
export type ScatterOptions = {
3946
radius: number;
4047
};
@@ -125,6 +132,7 @@ export type AxisProps<
125132
yTicksNormalized: number[];
126133
xScale: ScaleLinear<number, number, never>;
127134
yScale: ScaleLinear<number, number, never>;
135+
axisScales?: AxisScales;
128136
font?: SkFont | null;
129137
lineColor?: Color | { grid: Color | { x: Color; y: Color }; frame: Color };
130138
lineWidth?:

0 commit comments

Comments
 (0)