Merge pull request 'Line Graph -- render component' (#39) from loewy/line-graph-render-component into master
Reviewed-on: railbird/rn-playground#39 Reviewed-by: Kat Huang <kkathuang@gmail.com>
This commit is contained in:
commit
ec0904ffb1
@ -31,7 +31,7 @@ interface Props {
|
||||
export const BarGraph: React.FC<Props> = ({ data, useCommonScale = false, testID, ...props }) => {
|
||||
if (!data) {
|
||||
return null
|
||||
}
|
||||
} // TODO:#38
|
||||
const {
|
||||
xValues,
|
||||
yData,
|
||||
@ -52,9 +52,9 @@ export const BarGraph: React.FC<Props> = ({ data, useCommonScale = false, testID
|
||||
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
|
||||
} = useGraphData(data, props);
|
||||
} = useGraphData(data, { includeColors: false, ...props});
|
||||
|
||||
// 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] }]
|
||||
@ -80,7 +80,6 @@ export const BarGraph: React.FC<Props> = ({ data, useCommonScale = false, testID
|
||||
style={graphStyles.flex}
|
||||
data={yData}
|
||||
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
|
||||
yAccessor={({ item }) => (item as unknown as { value: number }).value}
|
||||
contentInset={contentInset}
|
||||
|
@ -19,7 +19,8 @@ export const graphStyles = StyleSheet.create({
|
||||
yAxisRightPadding: { paddingLeft: 3 },
|
||||
yAxisFontStyle: { fontSize: 10, fill: 'grey' },
|
||||
xAxisFontStyle: { fontSize: 12, fill: 'black' },
|
||||
xAxisMarginTop: { marginTop: -15 }
|
||||
xAxisMarginTop: { marginTop: -15 },
|
||||
horizontalInset: { right: 10, left: 10 },
|
||||
});
|
||||
|
||||
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 = {
|
||||
height: 300,
|
||||
spacingInner: 0.3,
|
||||
@ -6,5 +7,7 @@ export const chartDefaults = {
|
||||
contentInset: { top: 30, bottom: 30 },
|
||||
numberOfTicks: 6,
|
||||
min: 0,
|
||||
barColors: ['#598EBB', '#F2D4BC', '#DB7878']
|
||||
barColors: ['#598EBB', '#F2D4BC', '#DB7878'],
|
||||
includeColors: true,
|
||||
lineStrokeWidth: 2
|
||||
};
|
@ -34,6 +34,7 @@ export interface YAxisProps {
|
||||
}
|
||||
export interface GraphProps {
|
||||
data: GraphData;
|
||||
includeColors?: boolean;
|
||||
height?: number;
|
||||
spacingInner?: number;
|
||||
spacingOuter?: number;
|
||||
@ -41,6 +42,37 @@ export interface GraphProps {
|
||||
min?: number;
|
||||
numberOfTicks?: number;
|
||||
barColors?: Array<string>;
|
||||
lineStrokeWidth?: number;
|
||||
useCommonScale?: boolean;
|
||||
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;
|
||||
maxRightYAxisValue: number;
|
||||
maxLeftYAxisValue: number;
|
||||
includeColors: boolean;
|
||||
barColors: Array<string>
|
||||
}
|
||||
) => {
|
||||
const xValues = graphData.xValues;
|
||||
@ -20,17 +22,18 @@ export const convertToGraphData = (
|
||||
|
||||
// scale data points according to max value
|
||||
const yData = graphData.yValues.map((yAxis, index) => {
|
||||
const maxValue =
|
||||
index === rightAxisIndex ? options.maxRightYAxisValue : options.maxLeftYAxisValue;
|
||||
const maxValue = index === rightAxisIndex ? options.maxRightYAxisValue : options.maxLeftYAxisValue;
|
||||
|
||||
// scale values as a percentage of the max value
|
||||
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 }));
|
||||
|
||||
return {
|
||||
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;
|
||||
|
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(
|
||||
() => convertToGraphData(graphData, defaultProps.yAxisProps),
|
||||
[graphData, defaultProps.yAxisProps]
|
||||
() => convertToGraphData(graphData, { ...defaultProps.yAxisProps, includeColors: defaultProps.includeColors, barColors: defaultProps.barColors}),
|
||||
[graphData, defaultProps.yAxisProps, defaultProps.includeColors, defaultProps.barColors]
|
||||
);
|
||||
|
||||
return {
|
||||
|
@ -39,3 +39,26 @@ export const graph_data_three_measures = {
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
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