add line graph, associated changes to utils/config/styles & test
This commit is contained in:
parent
d9a4247b8e
commit
b80b05fbd8
@ -31,7 +31,7 @@ interface Props {
|
|||||||
export const BarGraph: React.FC<Props> = ({ data, useCommonScale = false, testID, ...props }) => {
|
export const BarGraph: React.FC<Props> = ({ data, useCommonScale = false, testID, ...props }) => {
|
||||||
if (!data) {
|
if (!data) {
|
||||||
return null
|
return null
|
||||||
}
|
} // TODO:#38
|
||||||
const {
|
const {
|
||||||
xValues,
|
xValues,
|
||||||
yData,
|
yData,
|
||||||
@ -52,9 +52,9 @@ export const BarGraph: React.FC<Props> = ({ data, useCommonScale = false, testID
|
|||||||
formatLeftYAxisLabel
|
formatLeftYAxisLabel
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Proper error/loading handling from useQueryHandler can work with this rule
|
// Proper error/loading handling from useQueryHandler can work with this rule #38
|
||||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||||
} = useGraphData(data, props);
|
} = useGraphData(data, { includeColors: false, ...props});
|
||||||
|
|
||||||
// TODO: Will come from BE & destructured / color assigned in useGraphData
|
// TODO: Will come from BE & destructured / color assigned in useGraphData
|
||||||
const yLabels = [{ displayName: 'Shots Taken', axis: 'LEFT' as 'LEFT', color: barColors[0] }, { displayName:'Make Percentage', axis: 'RIGHT' as 'RIGHT', color: barColors[1] }]
|
const yLabels = [{ displayName: 'Shots Taken', axis: 'LEFT' as 'LEFT', color: barColors[0] }, { displayName:'Make Percentage', axis: 'RIGHT' as 'RIGHT', color: barColors[1] }]
|
||||||
@ -80,7 +80,6 @@ export const BarGraph: React.FC<Props> = ({ data, useCommonScale = false, testID
|
|||||||
style={graphStyles.flex}
|
style={graphStyles.flex}
|
||||||
data={yData}
|
data={yData}
|
||||||
gridMin={min}
|
gridMin={min}
|
||||||
svg={{ stroke: 'transparent' }} // might want to do the transparent from here - if so may be best built as coming from defaultConfig
|
|
||||||
numberOfTicks={numberOfTicks} // rethink numberOfTicks, it should be determined automatically if we do our y axis scaling properly
|
numberOfTicks={numberOfTicks} // rethink numberOfTicks, it should be determined automatically if we do our y axis scaling properly
|
||||||
yAccessor={({ item }) => (item as unknown as { value: number }).value}
|
yAccessor={({ item }) => (item as unknown as { value: number }).value}
|
||||||
contentInset={contentInset}
|
contentInset={contentInset}
|
||||||
|
@ -19,7 +19,8 @@ export const graphStyles = StyleSheet.create({
|
|||||||
yAxisRightPadding: { paddingLeft: 3 },
|
yAxisRightPadding: { paddingLeft: 3 },
|
||||||
yAxisFontStyle: { fontSize: 10, fill: 'grey' },
|
yAxisFontStyle: { fontSize: 10, fill: 'grey' },
|
||||||
xAxisFontStyle: { fontSize: 12, fill: 'black' },
|
xAxisFontStyle: { fontSize: 12, fill: 'black' },
|
||||||
xAxisMarginTop: { marginTop: -15 }
|
xAxisMarginTop: { marginTop: -15 },
|
||||||
|
horizontalInset: { right: 10, left: 10 },
|
||||||
});
|
});
|
||||||
|
|
||||||
export const chartLabel = StyleSheet.create({
|
export const chartLabel = StyleSheet.create({
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
// if values in chartDefaults should not be accessible as props & are a style, move to graphStyles
|
// TODO: Style values should be moved to styles
|
||||||
|
// non-style config can live here as chartDefaults
|
||||||
export const chartDefaults = {
|
export const chartDefaults = {
|
||||||
height: 300,
|
height: 300,
|
||||||
spacingInner: 0.3,
|
spacingInner: 0.3,
|
||||||
@ -6,5 +7,7 @@ export const chartDefaults = {
|
|||||||
contentInset: { top: 30, bottom: 30 },
|
contentInset: { top: 30, bottom: 30 },
|
||||||
numberOfTicks: 6,
|
numberOfTicks: 6,
|
||||||
min: 0,
|
min: 0,
|
||||||
barColors: ['#598EBB', '#F2D4BC', '#DB7878']
|
barColors: ['#598EBB', '#F2D4BC', '#DB7878'],
|
||||||
|
includeColors: true,
|
||||||
|
lineStrokeWidth: 2
|
||||||
};
|
};
|
@ -34,6 +34,7 @@ export interface YAxisProps {
|
|||||||
}
|
}
|
||||||
export interface GraphProps {
|
export interface GraphProps {
|
||||||
data: GraphData;
|
data: GraphData;
|
||||||
|
includeColors?: boolean;
|
||||||
height?: number;
|
height?: number;
|
||||||
spacingInner?: number;
|
spacingInner?: number;
|
||||||
spacingOuter?: number;
|
spacingOuter?: number;
|
||||||
@ -41,6 +42,37 @@ export interface GraphProps {
|
|||||||
min?: number;
|
min?: number;
|
||||||
numberOfTicks?: number;
|
numberOfTicks?: number;
|
||||||
barColors?: Array<string>;
|
barColors?: Array<string>;
|
||||||
|
lineStrokeWidth?: number;
|
||||||
useCommonScale?: boolean;
|
useCommonScale?: boolean;
|
||||||
yAxisProps?: YAxisProps;
|
yAxisProps?: YAxisProps;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Common properties for graph render components.
|
||||||
|
*
|
||||||
|
* @interface CommonProps
|
||||||
|
* @property {GraphData} data - The primary data source for the graph.
|
||||||
|
* @property {number} [height] - Optional. The height of the graph.
|
||||||
|
* @property {number} [spacingInner] - Optional. The inner spacing between elements in the graph.
|
||||||
|
* @property {number} [spacingOuter] - Optional. The outer spacing of the elements in the graph.
|
||||||
|
* @property {{ top: number; bottom: number }} [contentInset] - Optional. Insets for the content of the graph.
|
||||||
|
* @property {number} [min] - Optional. The minimum value represented on the graph.
|
||||||
|
* @property {number} [numberOfTicks] - Optional. The number of tick marks along the axis.
|
||||||
|
* @property {Array<string>} [barColors] - Optional. Colors used for bars in the graph.
|
||||||
|
* @property {boolean} [useCommonScale] - Optional. Flag to use a common scale across multiple components.
|
||||||
|
* @property {YAxisProps} [yAxisProps] - Optional. Properties for the Y-axis of the graph.
|
||||||
|
* @property {string} [testID] - Optional. Identifier for testing purposes.
|
||||||
|
*/
|
||||||
|
export interface CommonProps {
|
||||||
|
data: GraphData;
|
||||||
|
height?: number;
|
||||||
|
spacingInner?: number;
|
||||||
|
spacingOuter?: number;
|
||||||
|
contentInset?: { top: number; bottom: number };
|
||||||
|
min?: number;
|
||||||
|
numberOfTicks?: number;
|
||||||
|
barColors?: Array<string>;
|
||||||
|
lineStrokeWidth?: number;
|
||||||
|
useCommonScale?: boolean;
|
||||||
|
yAxisProps?: YAxisProps;
|
||||||
|
testID?: string;
|
||||||
}
|
}
|
@ -8,6 +8,8 @@ export const convertToGraphData = (
|
|||||||
selectedRightYAxisLabel?: string;
|
selectedRightYAxisLabel?: string;
|
||||||
maxRightYAxisValue: number;
|
maxRightYAxisValue: number;
|
||||||
maxLeftYAxisValue: number;
|
maxLeftYAxisValue: number;
|
||||||
|
includeColors: boolean;
|
||||||
|
barColors: Array<string>
|
||||||
}
|
}
|
||||||
) => {
|
) => {
|
||||||
const xValues = graphData.xValues;
|
const xValues = graphData.xValues;
|
||||||
@ -20,17 +22,18 @@ export const convertToGraphData = (
|
|||||||
|
|
||||||
// scale data points according to max value
|
// scale data points according to max value
|
||||||
const yData = graphData.yValues.map((yAxis, index) => {
|
const yData = graphData.yValues.map((yAxis, index) => {
|
||||||
const maxValue =
|
const maxValue = index === rightAxisIndex ? options.maxRightYAxisValue : options.maxLeftYAxisValue;
|
||||||
index === rightAxisIndex ? options.maxRightYAxisValue : options.maxLeftYAxisValue;
|
|
||||||
|
|
||||||
// scale values as a percentage of the max value
|
// scale values as a percentage of the max value
|
||||||
const scaledValues = yAxis.values.map((value) => (value / maxValue) * 100);
|
const scaledValues = yAxis.values.map((value) => (value / maxValue) * 100);
|
||||||
|
|
||||||
|
const strokeColor = options.includeColors && options.barColors ? options.barColors[index % options.barColors.length] : 'transparent';
|
||||||
|
|
||||||
const mappedData = scaledValues.map((scaledValue) => ({ value: scaledValue }));
|
const mappedData = scaledValues.map((scaledValue) => ({ value: scaledValue }));
|
||||||
|
|
||||||
return {
|
return {
|
||||||
data: mappedData,
|
data: mappedData,
|
||||||
svg: { fill: 'transparent' } // fill handled in CustomBars
|
svg: { fill: 'transparent', stroke: strokeColor } // Apply the stroke color here
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
const yAxisLeftLabels = leftAxisIndex !== -1 ? graphData.yValues[leftAxisIndex] : null;
|
const yAxisLeftLabels = leftAxisIndex !== -1 ? graphData.yValues[leftAxisIndex] : null;
|
||||||
|
90
component/charts/line-graph/line-graph.tsx
Normal file
90
component/charts/line-graph/line-graph.tsx
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import * as shape from 'd3-shape';
|
||||||
|
import { View } from 'react-native';
|
||||||
|
import { LineChart, XAxis, YAxis } from 'react-native-svg-charts';
|
||||||
|
|
||||||
|
import { useGraphData } from '../use-graph-data';
|
||||||
|
import { CustomGrid } from '../custom-grid';
|
||||||
|
import { graphStyles } from '../chart-styles';
|
||||||
|
import ChartView from '../chart-view';
|
||||||
|
import { CommonProps } from '../graph-types';
|
||||||
|
|
||||||
|
// TODO: 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);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ChartView>
|
||||||
|
<View style={[graphStyles.rowContainer, { height: height }]} testID={`line-graph-${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}>
|
||||||
|
<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>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default LineGraph;
|
@ -43,8 +43,8 @@ export const useGraphData = (graphData: GraphData, props: Partial<GraphProps>):
|
|||||||
};
|
};
|
||||||
|
|
||||||
const { yData, yAxisLeftLabels, yAxisRightLabels, xValues } = useMemo(
|
const { yData, yAxisLeftLabels, yAxisRightLabels, xValues } = useMemo(
|
||||||
() => convertToGraphData(graphData, defaultProps.yAxisProps),
|
() => convertToGraphData(graphData, { ...defaultProps.yAxisProps, includeColors: defaultProps.includeColors, barColors: defaultProps.barColors}),
|
||||||
[graphData, defaultProps.yAxisProps]
|
[graphData, defaultProps.yAxisProps, defaultProps.includeColors, defaultProps.barColors]
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -38,4 +38,27 @@ export const graph_data_three_measures = {
|
|||||||
values: [77, 32, 45, 65, 50]
|
values: [77, 32, 45, 65, 50]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
export const line_chart_one_y_data = {
|
||||||
|
xValues: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
|
||||||
|
yValues: [
|
||||||
|
{
|
||||||
|
key: 'measure_1',
|
||||||
|
values: [100, 140, 90, 80, 40, 20, 70, 20, 30, 30]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
export const line_chart_two_y_data = {
|
||||||
|
xValues: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
|
||||||
|
yValues: [
|
||||||
|
{
|
||||||
|
key: 'measure_1',
|
||||||
|
values: [100, 140, 90, 80, 40, 20, 70, 20, 30, 30]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'measure_2',
|
||||||
|
values: [50, 67, 123, 140, 156, 147, 126, 180, 123, 87]
|
||||||
|
}
|
||||||
|
]
|
||||||
};
|
};
|
21
test/component/line-graph.test.tsx
Normal file
21
test/component/line-graph.test.tsx
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { render } from '@testing-library/react-native';
|
||||||
|
import LineGraph from '../../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();
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
Loading…
Reference in New Issue
Block a user