Merge pull request 'ChartContainer -- axis render parent component for graphs' (#42) from loewy/chart-container-wrapper into master
Reviewed-on: railbird/railbird-mobile#42 Reviewed-by: Kat Huang <kkathuang@gmail.com>
This commit is contained in:
commit
dc5a90cf1a
@ -26,7 +26,7 @@ interface Props {
|
||||
testID?: string;
|
||||
}
|
||||
|
||||
// TODO: separate PR will update useGraphData to take into account useCommonScale
|
||||
// TODO: #43 #31 separate PR will update useGraphData to take into account useCommonScale
|
||||
// eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars
|
||||
export const BarGraph: React.FC<Props> = ({
|
||||
data,
|
||||
|
99
src/component/charts/container/chart-container.tsx
Normal file
99
src/component/charts/container/chart-container.tsx
Normal file
@ -0,0 +1,99 @@
|
||||
import React from "react";
|
||||
import { View } from "react-native";
|
||||
import { XAxis, YAxis } from "react-native-svg-charts";
|
||||
import { graphStyles } from "../chart-styles";
|
||||
import ChartView from "../chart-view";
|
||||
import { ChartContainerProps } from "../graph-types";
|
||||
import { useGraphData } from "../use-graph-data";
|
||||
|
||||
// TODO: #43 #31 separate PR will update useGraphData to take into account useCommonScale
|
||||
// eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars
|
||||
const ChartContainer: React.FC<ChartContainerProps> = ({
|
||||
ChartComponent,
|
||||
data,
|
||||
useCommonScale = false,
|
||||
testID,
|
||||
...props
|
||||
}) => {
|
||||
if (!data || typeof data !== "object") {
|
||||
return null;
|
||||
} // TODO:#38
|
||||
const {
|
||||
xValues,
|
||||
yData,
|
||||
yAxisLeftLabels,
|
||||
yAxisRightLabels,
|
||||
defaultProps: {
|
||||
height,
|
||||
contentInset,
|
||||
min,
|
||||
numberOfTicks,
|
||||
lineStrokeWidth,
|
||||
yAxisProps: {
|
||||
maxLeftYAxisValue,
|
||||
maxRightYAxisValue,
|
||||
formatLeftYAxisLabel,
|
||||
formatRightYAxisLabel,
|
||||
},
|
||||
},
|
||||
// Proper error/loading handling from useQueryHandler can work with this rule #38
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
} = useGraphData(data, props);
|
||||
|
||||
// TODO: This could be done by the hook since it already handles spreading props.
|
||||
// Depending on if we want a context provider for Charts, that could be another way to handle it
|
||||
const chartComponentProps = {
|
||||
yData,
|
||||
xValues,
|
||||
lineStrokeWidth,
|
||||
min,
|
||||
contentInset,
|
||||
numberOfTicks,
|
||||
};
|
||||
return (
|
||||
<ChartView>
|
||||
<View
|
||||
style={[graphStyles.rowContainer, { height: height }]}
|
||||
testID={`chart-container-${testID}`}
|
||||
>
|
||||
<YAxis
|
||||
data={yAxisLeftLabels.values}
|
||||
contentInset={contentInset}
|
||||
svg={graphStyles.yAxisFontStyle}
|
||||
style={graphStyles.yAxisLeftPadding}
|
||||
min={min}
|
||||
max={maxLeftYAxisValue}
|
||||
numberOfTicks={numberOfTicks}
|
||||
formatLabel={formatLeftYAxisLabel}
|
||||
/>
|
||||
<View style={graphStyles.flex}>
|
||||
<ChartComponent {...chartComponentProps} />
|
||||
<XAxis
|
||||
data={xValues.map((_, index: number) => index)} // TODO: update when useGraphHook returns explicit display values
|
||||
formatLabel={(_, index) => xValues[index]}
|
||||
style={[graphStyles.xAxisMarginTop]}
|
||||
svg={graphStyles.xAxisFontStyle}
|
||||
numberOfTicks={numberOfTicks} // need a dynamic way of setting number of ticks
|
||||
contentInset={graphStyles.horizontalInset}
|
||||
// scale={scale.scaleBand} // Need to know the type of ChartComponent
|
||||
/>
|
||||
</View>
|
||||
<YAxis
|
||||
data={yAxisRightLabels?.values ?? yAxisLeftLabels.values}
|
||||
contentInset={contentInset}
|
||||
svg={graphStyles.yAxisFontStyle}
|
||||
style={[
|
||||
graphStyles.yAxisRightPadding,
|
||||
{ height: useCommonScale ? 0 : "auto" },
|
||||
]}
|
||||
min={min}
|
||||
max={maxRightYAxisValue}
|
||||
numberOfTicks={numberOfTicks}
|
||||
formatLabel={formatRightYAxisLabel}
|
||||
/>
|
||||
</View>
|
||||
</ChartView>
|
||||
);
|
||||
};
|
||||
|
||||
export default ChartContainer;
|
@ -63,7 +63,6 @@ export interface GraphProps {
|
||||
* @property {string} [testID] - Optional. Identifier for testing purposes.
|
||||
*/
|
||||
export interface CommonProps {
|
||||
data: GraphData;
|
||||
height?: number;
|
||||
spacingInner?: number;
|
||||
spacingOuter?: number;
|
||||
@ -75,4 +74,16 @@ export interface CommonProps {
|
||||
useCommonScale?: boolean;
|
||||
yAxisProps?: YAxisProps;
|
||||
testID?: string;
|
||||
yData?: { data: { value: number }[]; svg: { fill: string } }[];
|
||||
xValues?: XValue[];
|
||||
}
|
||||
/**
|
||||
* Common properties for graph render components.
|
||||
*
|
||||
* @interface ChartContainerProps
|
||||
* @property {React.ComponentType<CommonProps>} ChartComponent - The graph component to render
|
||||
*/
|
||||
export interface ChartContainerProps extends CommonProps {
|
||||
ChartComponent: React.ComponentType<CommonProps>;
|
||||
data: GraphData;
|
||||
}
|
||||
|
@ -1,102 +1,31 @@
|
||||
import * as shape from "d3-shape";
|
||||
import React from "react";
|
||||
import { View } from "react-native";
|
||||
import { LineChart, XAxis, YAxis } from "react-native-svg-charts";
|
||||
|
||||
import { LineChart } from "react-native-svg-charts";
|
||||
import { graphStyles } from "../chart-styles";
|
||||
import ChartView from "../chart-view";
|
||||
import { CustomGrid } from "../custom-grid";
|
||||
import { CommonProps } from "../graph-types";
|
||||
import { useGraphData } from "../use-graph-data";
|
||||
|
||||
// TODO: separate PR will update useGraphData to take into account useCommonScale
|
||||
// TODO: #43 #31 separate PR will update useGraphData to take into account useCommonScale
|
||||
// eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars
|
||||
const LineGraph: React.FC<CommonProps> = ({
|
||||
data,
|
||||
useCommonScale = false,
|
||||
testID,
|
||||
...props
|
||||
}) => {
|
||||
if (!data || typeof data !== "object") {
|
||||
return null;
|
||||
} // TODO:#38
|
||||
const {
|
||||
xValues,
|
||||
yData,
|
||||
yAxisLeftLabels,
|
||||
yAxisRightLabels,
|
||||
defaultProps: {
|
||||
height,
|
||||
contentInset,
|
||||
min,
|
||||
numberOfTicks,
|
||||
lineStrokeWidth,
|
||||
yAxisProps: {
|
||||
maxLeftYAxisValue,
|
||||
maxRightYAxisValue,
|
||||
formatLeftYAxisLabel,
|
||||
formatRightYAxisLabel,
|
||||
},
|
||||
},
|
||||
// Proper error/loading handling from useQueryHandler can work with this rule #38
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
} = useGraphData(data, props);
|
||||
|
||||
const LineGraph: React.FC<CommonProps> = ({ ...chartComponentProps }) => {
|
||||
const { yData, xValues, lineStrokeWidth, min, contentInset, numberOfTicks } =
|
||||
chartComponentProps;
|
||||
return (
|
||||
<ChartView>
|
||||
<View
|
||||
style={[graphStyles.rowContainer, { height: height }]}
|
||||
testID={`line-graph-${testID}`}
|
||||
<>
|
||||
<LineChart
|
||||
style={graphStyles.flex}
|
||||
data={yData}
|
||||
curve={shape.curveNatural}
|
||||
svg={{ strokeWidth: lineStrokeWidth }}
|
||||
yAccessor={({ item }) => (item as unknown as { value: number }).value}
|
||||
xAccessor={({ index }) => xValues[index] as number}
|
||||
gridMin={min}
|
||||
contentInset={contentInset}
|
||||
numberOfTicks={numberOfTicks}
|
||||
>
|
||||
<YAxis
|
||||
data={yAxisLeftLabels.values}
|
||||
contentInset={contentInset}
|
||||
svg={graphStyles.yAxisFontStyle}
|
||||
style={graphStyles.yAxisLeftPadding}
|
||||
min={min}
|
||||
max={maxLeftYAxisValue}
|
||||
numberOfTicks={numberOfTicks}
|
||||
formatLabel={formatLeftYAxisLabel}
|
||||
/>
|
||||
<View style={graphStyles.flex}>
|
||||
<LineChart
|
||||
style={graphStyles.flex}
|
||||
data={yData}
|
||||
curve={shape.curveNatural}
|
||||
svg={{ strokeWidth: lineStrokeWidth }}
|
||||
yAccessor={({ item }) =>
|
||||
(item as unknown as { value: number }).value
|
||||
}
|
||||
xAccessor={({ index }) => xValues[index] as number}
|
||||
gridMin={min}
|
||||
contentInset={contentInset}
|
||||
numberOfTicks={numberOfTicks}
|
||||
>
|
||||
<CustomGrid />
|
||||
</LineChart>
|
||||
<XAxis
|
||||
data={xValues.map((_, index: number) => index)} // TODO: update when useGraphHook returns explicit display values
|
||||
style={[graphStyles.xAxisMarginTop]}
|
||||
svg={graphStyles.xAxisFontStyle}
|
||||
numberOfTicks={numberOfTicks}
|
||||
contentInset={graphStyles.horizontalInset}
|
||||
/>
|
||||
</View>
|
||||
<YAxis
|
||||
data={yAxisRightLabels?.values ?? yAxisLeftLabels.values}
|
||||
contentInset={contentInset}
|
||||
svg={graphStyles.yAxisFontStyle}
|
||||
style={[
|
||||
graphStyles.yAxisRightPadding,
|
||||
{ height: useCommonScale ? 0 : "auto" },
|
||||
]}
|
||||
min={min}
|
||||
max={maxRightYAxisValue}
|
||||
numberOfTicks={numberOfTicks}
|
||||
formatLabel={formatRightYAxisLabel} // formatRightYAxisLabel formatting could come from yAxisRightLabels object
|
||||
/>
|
||||
</View>
|
||||
</ChartView>
|
||||
<CustomGrid />
|
||||
</LineChart>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -1,14 +1,15 @@
|
||||
import React from "react";
|
||||
import { StyleSheet, View } from "react-native";
|
||||
import { graph_data_two_measures } from "../../test/mock/charts/mock-data";
|
||||
import BarGraph from "../component/charts/bar-graph/bar-graph";
|
||||
import { line_chart_two_y_data } from "../../test/mock/charts/mock-data";
|
||||
import ChartContainer from "../component/charts/container/chart-container";
|
||||
import LineGraph from "../component/charts/line-graph/line-graph";
|
||||
|
||||
// Session Mock - can be used for session summary screen using a query handler component
|
||||
// BarGraph component using mocked data currently
|
||||
export default function SessionScreen() {
|
||||
return (
|
||||
<View style={StyleSheet.absoluteFill}>
|
||||
<BarGraph data={graph_data_two_measures} />
|
||||
<ChartContainer data={line_chart_two_y_data} ChartComponent={LineGraph} />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
24
test/component/chart-container.test.tsx
Normal file
24
test/component/chart-container.test.tsx
Normal file
@ -0,0 +1,24 @@
|
||||
import { render } from "@testing-library/react-native";
|
||||
import React from "react";
|
||||
import ChartContainer from "../../src/component/charts/container/chart-container";
|
||||
import LineGraph from "../../src/component/charts/line-graph/line-graph";
|
||||
import { line_chart_two_y_data } from "../mock/charts/mock-data";
|
||||
describe("ChartContainer Component Tests", () => {
|
||||
it("renders correctly with data -- line graph", () => {
|
||||
const { getByTestId } = render(
|
||||
<ChartContainer
|
||||
data={line_chart_two_y_data}
|
||||
testID="1"
|
||||
ChartComponent={LineGraph}
|
||||
/>,
|
||||
);
|
||||
expect(getByTestId(`chart-container-1`)).toBeTruthy();
|
||||
});
|
||||
|
||||
it("does not render without data props", () => {
|
||||
// Have to ts-ignore to test null data conditions
|
||||
// @ts-ignore
|
||||
const { queryByTestId } = render(<ChartContainer testID="2" />);
|
||||
expect(queryByTestId(`chart-container-2`)).toBeNull();
|
||||
});
|
||||
});
|
@ -1,20 +0,0 @@
|
||||
import { render } from "@testing-library/react-native";
|
||||
import React from "react";
|
||||
import LineGraph from "../../src/component/charts/line-graph/line-graph";
|
||||
import { line_chart_two_y_data } from "../mock/charts/mock-data";
|
||||
|
||||
describe("LineGraph Component Tests", () => {
|
||||
it("renders correctly with data", () => {
|
||||
const { getByTestId } = render(
|
||||
<LineGraph data={line_chart_two_y_data} testID="1" />,
|
||||
);
|
||||
expect(getByTestId(`line-graph-1`)).toBeTruthy();
|
||||
});
|
||||
|
||||
it("does not render without data", () => {
|
||||
// Have to ts-ignore to test null data conditions
|
||||
// @ts-ignore
|
||||
const { queryByTestId } = render(<LineGraph testID="2" />);
|
||||
expect(queryByTestId(`line-graph-2`)).toBeNull();
|
||||
});
|
||||
});
|
@ -62,3 +62,38 @@ export const line_chart_two_y_data = {
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const mockYData = [
|
||||
{
|
||||
data: [
|
||||
{ value: 71.42857142857143 },
|
||||
{ value: 100 },
|
||||
{ value: 64.28571428571429 },
|
||||
{ value: 57.14285714285714 },
|
||||
{ value: 28.57142857142857 },
|
||||
{ value: 14.285714285714285 },
|
||||
{ value: 50 },
|
||||
{ value: 14.285714285714285 },
|
||||
{ value: 21.428571428571427 },
|
||||
{ value: 21.428571428571427 },
|
||||
],
|
||||
svg: { fill: "transparent", stroke: "#598EBB" },
|
||||
},
|
||||
{
|
||||
data: [
|
||||
{ value: 27.77777777777778 },
|
||||
{ value: 37.22222222222222 },
|
||||
{ value: 68.33333333333333 },
|
||||
{ value: 77.77777777777779 },
|
||||
{ value: 86.66666666666667 },
|
||||
{ value: 81.66666666666667 },
|
||||
{ value: 70 },
|
||||
{ value: 100 },
|
||||
{ value: 68.33333333333333 },
|
||||
{ value: 48.333333333333336 },
|
||||
],
|
||||
svg: { fill: "transparent", stroke: "#F2D4BC" },
|
||||
},
|
||||
];
|
||||
|
||||
export const mockXValues = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
|
||||
|
Loading…
Reference in New Issue
Block a user