Actually respect prettier configuration
This commit is contained in:
@@ -1,116 +1,137 @@
|
||||
import React from 'react';
|
||||
import * as scale from 'd3-scale';
|
||||
import { View } from 'react-native';
|
||||
import { BarChart, XAxis, YAxis } from 'react-native-svg-charts';
|
||||
import React from "react";
|
||||
import * as scale from "d3-scale";
|
||||
import { View } from "react-native";
|
||||
import { BarChart, XAxis, YAxis } from "react-native-svg-charts";
|
||||
|
||||
import { useGraphData } from '../use-graph-data';
|
||||
import { GraphData, YAxisProps } from '../graph-types';
|
||||
import { useGraphData } from "../use-graph-data";
|
||||
import { GraphData, YAxisProps } from "../graph-types";
|
||||
|
||||
import { CustomBars } from '../custom-bars';
|
||||
import { CustomGrid } from '../custom-grid';
|
||||
import { graphStyles } from '../chart-styles';
|
||||
import ChartView from '../chart-view';
|
||||
import ChartLabel from '../chart-label/chart-label';
|
||||
import { CustomBars } from "../custom-bars";
|
||||
import { CustomGrid } from "../custom-grid";
|
||||
import { graphStyles } from "../chart-styles";
|
||||
import ChartView from "../chart-view";
|
||||
import ChartLabel from "../chart-label/chart-label";
|
||||
|
||||
interface Props {
|
||||
data: GraphData;
|
||||
height?: number;
|
||||
spacingInner?: number;
|
||||
spacingOuter?: number;
|
||||
contentInset?: { top: number; bottom: number };
|
||||
min?: number;
|
||||
numberOfTicks?: number;
|
||||
barColors?: Array<string>;
|
||||
useCommonScale?: boolean;
|
||||
yAxisProps?: YAxisProps;
|
||||
testID?: string;
|
||||
data: GraphData;
|
||||
height?: number;
|
||||
spacingInner?: number;
|
||||
spacingOuter?: number;
|
||||
contentInset?: { top: number; bottom: number };
|
||||
min?: number;
|
||||
numberOfTicks?: number;
|
||||
barColors?: Array<string>;
|
||||
useCommonScale?: boolean;
|
||||
yAxisProps?: YAxisProps;
|
||||
testID?: string;
|
||||
}
|
||||
|
||||
// TODO: 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, useCommonScale = false, testID, ...props }) => {
|
||||
if (!data) {
|
||||
return null
|
||||
} // TODO:#38
|
||||
const {
|
||||
xValues,
|
||||
yData,
|
||||
yAxisRightLabels,
|
||||
yAxisLeftLabels,
|
||||
defaultProps: {
|
||||
height,
|
||||
spacingInner,
|
||||
spacingOuter,
|
||||
contentInset,
|
||||
min,
|
||||
numberOfTicks,
|
||||
barColors,
|
||||
yAxisProps: {
|
||||
maxLeftYAxisValue,
|
||||
maxRightYAxisValue,
|
||||
formatRightYAxisLabel,
|
||||
formatLeftYAxisLabel
|
||||
}
|
||||
}
|
||||
// Proper error/loading handling from useQueryHandler can work with this rule #38
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
} = useGraphData(data, { includeColors: false, ...props});
|
||||
export const BarGraph: React.FC<Props> = ({
|
||||
data,
|
||||
useCommonScale = false,
|
||||
testID,
|
||||
...props
|
||||
}) => {
|
||||
if (!data) {
|
||||
return null;
|
||||
} // TODO:#38
|
||||
const {
|
||||
xValues,
|
||||
yData,
|
||||
yAxisRightLabels,
|
||||
yAxisLeftLabels,
|
||||
defaultProps: {
|
||||
height,
|
||||
spacingInner,
|
||||
spacingOuter,
|
||||
contentInset,
|
||||
min,
|
||||
numberOfTicks,
|
||||
barColors,
|
||||
yAxisProps: {
|
||||
maxLeftYAxisValue,
|
||||
maxRightYAxisValue,
|
||||
formatRightYAxisLabel,
|
||||
formatLeftYAxisLabel,
|
||||
},
|
||||
},
|
||||
// Proper error/loading handling from useQueryHandler can work with this rule #38
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
} = 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] }]
|
||||
const title = 'Shots Taken / Make Percentage by Cut Angle'
|
||||
// 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 title = "Shots Taken / Make Percentage by Cut Angle";
|
||||
|
||||
return (
|
||||
<ChartView>
|
||||
<ChartLabel title={title} yLabels={yLabels} />
|
||||
<View style={[graphStyles.rowContainer, { height: height }]} testID={`bar-graph-${testID}`}>
|
||||
<YAxis
|
||||
data={yAxisLeftLabels.values}
|
||||
contentInset={contentInset}
|
||||
svg={graphStyles.yAxisFontStyle}
|
||||
style={graphStyles.yAxisLeftPadding}
|
||||
min={min}
|
||||
max={maxLeftYAxisValue}
|
||||
numberOfTicks={numberOfTicks}
|
||||
formatLabel={formatLeftYAxisLabel}
|
||||
/>
|
||||
return (
|
||||
<ChartView>
|
||||
<ChartLabel title={title} yLabels={yLabels} />
|
||||
<View
|
||||
style={[graphStyles.rowContainer, { height: height }]}
|
||||
testID={`bar-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}>
|
||||
<BarChart
|
||||
style={graphStyles.flex}
|
||||
data={yData}
|
||||
gridMin={min}
|
||||
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}
|
||||
spacingInner={spacingInner}
|
||||
spacingOuter={spacingOuter}
|
||||
>
|
||||
<CustomGrid />
|
||||
<CustomBars barData={yData} xValues={xValues} barColors={barColors} />
|
||||
</BarChart>
|
||||
<XAxis
|
||||
data={xValues.map((_, index) => index)}
|
||||
formatLabel={(_, index) => xValues[index]}
|
||||
style={graphStyles.xAxisMarginTop}
|
||||
svg={graphStyles.xAxisFontStyle}
|
||||
scale={scale.scaleBand}
|
||||
/>
|
||||
</View>
|
||||
<View style={graphStyles.flex}>
|
||||
<BarChart
|
||||
style={graphStyles.flex}
|
||||
data={yData}
|
||||
gridMin={min}
|
||||
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}
|
||||
spacingInner={spacingInner}
|
||||
spacingOuter={spacingOuter}
|
||||
>
|
||||
<CustomGrid />
|
||||
<CustomBars
|
||||
barData={yData}
|
||||
xValues={xValues}
|
||||
barColors={barColors}
|
||||
/>
|
||||
</BarChart>
|
||||
<XAxis
|
||||
data={xValues.map((_, index) => index)}
|
||||
formatLabel={(_, index) => xValues[index]}
|
||||
style={graphStyles.xAxisMarginTop}
|
||||
svg={graphStyles.xAxisFontStyle}
|
||||
scale={scale.scaleBand}
|
||||
/>
|
||||
</View>
|
||||
|
||||
<YAxis
|
||||
data={yAxisRightLabels?.values ?? yAxisLeftLabels.values}
|
||||
contentInset={contentInset}
|
||||
svg={graphStyles.yAxisFontStyle}
|
||||
style={graphStyles.yAxisRightPadding}
|
||||
min={min}
|
||||
max={maxRightYAxisValue}
|
||||
numberOfTicks={numberOfTicks}
|
||||
formatLabel={formatRightYAxisLabel}
|
||||
/>
|
||||
</View>
|
||||
</ChartView>
|
||||
);
|
||||
<YAxis
|
||||
data={yAxisRightLabels?.values ?? yAxisLeftLabels.values}
|
||||
contentInset={contentInset}
|
||||
svg={graphStyles.yAxisFontStyle}
|
||||
style={graphStyles.yAxisRightPadding}
|
||||
min={min}
|
||||
max={maxRightYAxisValue}
|
||||
numberOfTicks={numberOfTicks}
|
||||
formatLabel={formatRightYAxisLabel}
|
||||
/>
|
||||
</View>
|
||||
</ChartView>
|
||||
);
|
||||
};
|
||||
|
||||
export default BarGraph;
|
||||
export default BarGraph;
|
||||
|
@@ -1,48 +1,48 @@
|
||||
import React from 'react';
|
||||
import { Path } from 'react-native-svg';
|
||||
import React from "react";
|
||||
import { Path } from "react-native-svg";
|
||||
|
||||
import { calculateBarOrigin, drawBarPath } from './custom-bar-utils';
|
||||
import { ScaleBandType, ScaleLinearType, } from './graph-types';
|
||||
import { calculateBarOrigin, drawBarPath } from "./custom-bar-utils";
|
||||
import { ScaleBandType, ScaleLinearType } from "./graph-types";
|
||||
|
||||
interface BarProps {
|
||||
scaleX: ScaleBandType;
|
||||
scaleY: ScaleLinearType;
|
||||
data: { value: number };
|
||||
barNumber: number;
|
||||
index: number;
|
||||
fill: string;
|
||||
barWidth: number;
|
||||
gap: number;
|
||||
roundedRadius: number;
|
||||
scaleX: ScaleBandType;
|
||||
scaleY: ScaleLinearType;
|
||||
data: { value: number };
|
||||
barNumber: number;
|
||||
index: number;
|
||||
fill: string;
|
||||
barWidth: number;
|
||||
gap: number;
|
||||
roundedRadius: number;
|
||||
}
|
||||
|
||||
export const Bar: React.FC<BarProps> = ({
|
||||
scaleX,
|
||||
scaleY,
|
||||
data,
|
||||
barNumber,
|
||||
index,
|
||||
fill,
|
||||
barWidth,
|
||||
gap,
|
||||
roundedRadius
|
||||
scaleX,
|
||||
scaleY,
|
||||
data,
|
||||
barNumber,
|
||||
index,
|
||||
fill,
|
||||
barWidth,
|
||||
gap,
|
||||
roundedRadius,
|
||||
}) => {
|
||||
const { xOrigin, yOrigin, height } = calculateBarOrigin({
|
||||
scaleX,
|
||||
scaleY,
|
||||
index,
|
||||
data,
|
||||
barNumber,
|
||||
barWidth,
|
||||
gap
|
||||
});
|
||||
const { xOrigin, yOrigin, height } = calculateBarOrigin({
|
||||
scaleX,
|
||||
scaleY,
|
||||
index,
|
||||
data,
|
||||
barNumber,
|
||||
barWidth,
|
||||
gap,
|
||||
});
|
||||
|
||||
return (
|
||||
<Path
|
||||
key={`bar-path-${barNumber}-${index}`}
|
||||
d={drawBarPath(xOrigin, yOrigin, barWidth, height, roundedRadius)}
|
||||
fill={fill}
|
||||
testID={`bar-${barNumber}-${index}`}
|
||||
/>
|
||||
);
|
||||
return (
|
||||
<Path
|
||||
key={`bar-path-${barNumber}-${index}`}
|
||||
d={drawBarPath(xOrigin, yOrigin, barWidth, height, roundedRadius)}
|
||||
fill={fill}
|
||||
testID={`bar-${barNumber}-${index}`}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
@@ -1,42 +1,44 @@
|
||||
import React from 'react';
|
||||
import { Text, View } from 'react-native';
|
||||
import React from "react";
|
||||
import { Text, View } from "react-native";
|
||||
|
||||
import { chartLabel } from '../chart-styles';
|
||||
import { chartLabel } from "../chart-styles";
|
||||
|
||||
type Axis = 'RIGHT' | 'LEFT'
|
||||
type Axis = "RIGHT" | "LEFT";
|
||||
|
||||
interface YLabel {
|
||||
axis: Axis;
|
||||
displayName: string;
|
||||
color: string;
|
||||
axis: Axis;
|
||||
displayName: string;
|
||||
color: string;
|
||||
}
|
||||
|
||||
type ChartLabelProps = {
|
||||
title: string;
|
||||
yLabels: Array<YLabel>;
|
||||
title: string;
|
||||
yLabels: Array<YLabel>;
|
||||
};
|
||||
|
||||
const renderLabels = (yLabels: Array<YLabel>) => {
|
||||
return yLabels.map((label) => (
|
||||
<View key={`${label.axis}-${label.displayName}`} style={chartLabel.labelInnerRow}>
|
||||
<View
|
||||
style={[chartLabel.labelColorBox, { backgroundColor: label.color }]}
|
||||
/>
|
||||
<View style={chartLabel.labelTextMargin}>
|
||||
<Text style={chartLabel.labelText}>{label.displayName}</Text>
|
||||
</View>
|
||||
</View>
|
||||
));
|
||||
return yLabels.map((label) => (
|
||||
<View
|
||||
key={`${label.axis}-${label.displayName}`}
|
||||
style={chartLabel.labelInnerRow}
|
||||
>
|
||||
<View
|
||||
style={[chartLabel.labelColorBox, { backgroundColor: label.color }]}
|
||||
/>
|
||||
<View style={chartLabel.labelTextMargin}>
|
||||
<Text style={chartLabel.labelText}>{label.displayName}</Text>
|
||||
</View>
|
||||
</View>
|
||||
));
|
||||
};
|
||||
|
||||
export default function ChartLabel({ title, yLabels }: ChartLabelProps) {
|
||||
|
||||
return (
|
||||
<View>
|
||||
<View style={chartLabel.titleRow}>
|
||||
<Text style={chartLabel.titleText}>{title}</Text>
|
||||
</View>
|
||||
<View style={chartLabel.labelOuterRow}>{renderLabels(yLabels)}</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<View>
|
||||
<View style={chartLabel.titleRow}>
|
||||
<Text style={chartLabel.titleText}>{title}</Text>
|
||||
</View>
|
||||
<View style={chartLabel.labelOuterRow}>{renderLabels(yLabels)}</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
@@ -1,46 +1,46 @@
|
||||
import { StyleSheet } from 'react-native';
|
||||
import { StyleSheet } from "react-native";
|
||||
|
||||
import { colors, shadows } from '../../styles';
|
||||
import { colors, shadows } from "../../styles";
|
||||
|
||||
export const graphStyles = StyleSheet.create({
|
||||
container: {
|
||||
backgroundColor: colors.panelWhite,
|
||||
borderColor: 'black',
|
||||
borderRadius: 5,
|
||||
marginVertical: 10,
|
||||
marginHorizontal: 15,
|
||||
paddingTop: 15,
|
||||
paddingHorizontal: 15,
|
||||
...shadows.standard
|
||||
},
|
||||
rowContainer: { flexDirection: 'row', padding: 10 },
|
||||
flex: { flex: 1 },
|
||||
yAxisLeftPadding: { paddingRight: 3 },
|
||||
yAxisRightPadding: { paddingLeft: 3 },
|
||||
yAxisFontStyle: { fontSize: 10, fill: 'grey' },
|
||||
xAxisFontStyle: { fontSize: 12, fill: 'black' },
|
||||
xAxisMarginTop: { marginTop: -15 },
|
||||
horizontalInset: { right: 10, left: 10 },
|
||||
container: {
|
||||
backgroundColor: colors.panelWhite,
|
||||
borderColor: "black",
|
||||
borderRadius: 5,
|
||||
marginVertical: 10,
|
||||
marginHorizontal: 15,
|
||||
paddingTop: 15,
|
||||
paddingHorizontal: 15,
|
||||
...shadows.standard,
|
||||
},
|
||||
rowContainer: { flexDirection: "row", padding: 10 },
|
||||
flex: { flex: 1 },
|
||||
yAxisLeftPadding: { paddingRight: 3 },
|
||||
yAxisRightPadding: { paddingLeft: 3 },
|
||||
yAxisFontStyle: { fontSize: 10, fill: "grey" },
|
||||
xAxisFontStyle: { fontSize: 12, fill: "black" },
|
||||
xAxisMarginTop: { marginTop: -15 },
|
||||
horizontalInset: { right: 10, left: 10 },
|
||||
});
|
||||
|
||||
export const chartLabel = StyleSheet.create({
|
||||
titleRow: {
|
||||
flexDirection: 'row',
|
||||
marginHorizontal: 10
|
||||
},
|
||||
titleText: { fontWeight: '500' },
|
||||
labelOuterRow: {
|
||||
flexDirection: 'row',
|
||||
flexWrap: 'wrap',
|
||||
marginHorizontal: 10
|
||||
},
|
||||
labelInnerRow: { flexDirection: 'row', alignItems: 'center', marginTop: 5 },
|
||||
labelColorBox: {
|
||||
height: 15,
|
||||
width: 15,
|
||||
borderRadius: 4,
|
||||
marginRight: 2
|
||||
},
|
||||
labelTextMargin: { marginRight: 15 },
|
||||
labelText: { fontSize: 12 }
|
||||
titleRow: {
|
||||
flexDirection: "row",
|
||||
marginHorizontal: 10,
|
||||
},
|
||||
titleText: { fontWeight: "500" },
|
||||
labelOuterRow: {
|
||||
flexDirection: "row",
|
||||
flexWrap: "wrap",
|
||||
marginHorizontal: 10,
|
||||
},
|
||||
labelInnerRow: { flexDirection: "row", alignItems: "center", marginTop: 5 },
|
||||
labelColorBox: {
|
||||
height: 15,
|
||||
width: 15,
|
||||
borderRadius: 4,
|
||||
marginRight: 2,
|
||||
},
|
||||
labelTextMargin: { marginRight: 15 },
|
||||
labelText: { fontSize: 12 },
|
||||
});
|
||||
|
@@ -1,16 +1,20 @@
|
||||
import React from 'react';
|
||||
import { StyleProp, View, ViewStyle } from 'react-native';
|
||||
import React from "react";
|
||||
import { StyleProp, View, ViewStyle } from "react-native";
|
||||
|
||||
import { graphStyles } from './chart-styles';
|
||||
import { graphStyles } from "./chart-styles";
|
||||
|
||||
interface ChartViewProps {
|
||||
style?: StyleProp<ViewStyle>;
|
||||
children: React.ReactNode;
|
||||
testID?: string;
|
||||
style?: StyleProp<ViewStyle>;
|
||||
children: React.ReactNode;
|
||||
testID?: string;
|
||||
}
|
||||
|
||||
const ChartView: React.FC<ChartViewProps> = ({ children, style, testID }) => {
|
||||
return <View style={[graphStyles.container, style]} testID={testID} >{children}</View>;
|
||||
return (
|
||||
<View style={[graphStyles.container, style]} testID={testID}>
|
||||
{children}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default ChartView;
|
||||
export default ChartView;
|
||||
|
@@ -1,59 +1,63 @@
|
||||
import { path as d3 } from 'd3-path';
|
||||
import { path as d3 } from "d3-path";
|
||||
|
||||
import { ScaleLinearType, ScaleBandType } from './graph-types';
|
||||
import { ScaleLinearType, ScaleBandType } from "./graph-types";
|
||||
|
||||
type BarCalculationProps = {
|
||||
scaleX: ScaleBandType;
|
||||
scaleY: ScaleLinearType;
|
||||
data: { value: number };
|
||||
barNumber: number;
|
||||
index: number;
|
||||
barWidth: number;
|
||||
gap: number;
|
||||
scaleX: ScaleBandType;
|
||||
scaleY: ScaleLinearType;
|
||||
data: { value: number };
|
||||
barNumber: number;
|
||||
index: number;
|
||||
barWidth: number;
|
||||
gap: number;
|
||||
};
|
||||
|
||||
export function calculateBarOrigin({
|
||||
scaleX,
|
||||
scaleY,
|
||||
barWidth,
|
||||
data,
|
||||
index,
|
||||
barNumber,
|
||||
gap
|
||||
scaleX,
|
||||
scaleY,
|
||||
barWidth,
|
||||
data,
|
||||
index,
|
||||
barNumber,
|
||||
gap,
|
||||
}: BarCalculationProps): { xOrigin: number; yOrigin: number; height: number } {
|
||||
const firstBar = barNumber === 0;
|
||||
const xOrigin = scaleX(index) + (firstBar ? 0 : barWidth * barNumber + gap * barNumber);
|
||||
const yOrigin = scaleY(data.value);
|
||||
const height = scaleY(0) - yOrigin;
|
||||
const firstBar = barNumber === 0;
|
||||
const xOrigin =
|
||||
scaleX(index) + (firstBar ? 0 : barWidth * barNumber + gap * barNumber);
|
||||
const yOrigin = scaleY(data.value);
|
||||
const height = scaleY(0) - yOrigin;
|
||||
|
||||
return { xOrigin, yOrigin, height };
|
||||
return { xOrigin, yOrigin, height };
|
||||
}
|
||||
|
||||
export function drawBarPath(
|
||||
xOrigin: number,
|
||||
yOrigin: number,
|
||||
barWidth: number,
|
||||
height: number,
|
||||
roundedRadius: number
|
||||
xOrigin: number,
|
||||
yOrigin: number,
|
||||
barWidth: number,
|
||||
height: number,
|
||||
roundedRadius: number,
|
||||
): string {
|
||||
const path = d3();
|
||||
path.moveTo(xOrigin, yOrigin + height);
|
||||
path.lineTo(xOrigin, yOrigin + roundedRadius);
|
||||
path.arcTo(xOrigin, yOrigin, xOrigin + roundedRadius, yOrigin, roundedRadius);
|
||||
path.lineTo(xOrigin + barWidth - roundedRadius, yOrigin);
|
||||
path.arcTo(
|
||||
xOrigin + barWidth,
|
||||
yOrigin,
|
||||
xOrigin + barWidth,
|
||||
yOrigin + roundedRadius,
|
||||
roundedRadius
|
||||
);
|
||||
path.lineTo(xOrigin + barWidth, yOrigin + height);
|
||||
path.lineTo(xOrigin, yOrigin + height);
|
||||
path.closePath();
|
||||
const path = d3();
|
||||
path.moveTo(xOrigin, yOrigin + height);
|
||||
path.lineTo(xOrigin, yOrigin + roundedRadius);
|
||||
path.arcTo(xOrigin, yOrigin, xOrigin + roundedRadius, yOrigin, roundedRadius);
|
||||
path.lineTo(xOrigin + barWidth - roundedRadius, yOrigin);
|
||||
path.arcTo(
|
||||
xOrigin + barWidth,
|
||||
yOrigin,
|
||||
xOrigin + barWidth,
|
||||
yOrigin + roundedRadius,
|
||||
roundedRadius,
|
||||
);
|
||||
path.lineTo(xOrigin + barWidth, yOrigin + height);
|
||||
path.lineTo(xOrigin, yOrigin + height);
|
||||
path.closePath();
|
||||
|
||||
return path.toString();
|
||||
return path.toString();
|
||||
}
|
||||
|
||||
export const calculateBarWidth = (bandwidth: number, combinedDataLength: number, gap: number) =>
|
||||
(bandwidth - gap * (combinedDataLength - 1)) / combinedDataLength;
|
||||
export const calculateBarWidth = (
|
||||
bandwidth: number,
|
||||
combinedDataLength: number,
|
||||
gap: number,
|
||||
) => (bandwidth - gap * (combinedDataLength - 1)) / combinedDataLength;
|
||||
|
@@ -1,54 +1,53 @@
|
||||
import { Svg } from "react-native-svg";
|
||||
|
||||
import { Svg } from 'react-native-svg';
|
||||
|
||||
import { Bar } from './bar';
|
||||
import { ScaleBandType, ScaleLinearType } from './graph-types';
|
||||
import { calculateBarWidth } from './custom-bar-utils';
|
||||
import { Bar } from "./bar";
|
||||
import { ScaleBandType, ScaleLinearType } from "./graph-types";
|
||||
import { calculateBarWidth } from "./custom-bar-utils";
|
||||
|
||||
export interface CombinedData {
|
||||
data: { value: number }[];
|
||||
svg: { fill: string };
|
||||
data: { value: number }[];
|
||||
svg: { fill: string };
|
||||
}
|
||||
|
||||
interface CustomBarsProps {
|
||||
x: ScaleBandType;
|
||||
y: ScaleLinearType;
|
||||
bandwidth: number;
|
||||
barColors: string[];
|
||||
xValues: unknown[]; // TODO: update this value when this data type is defined
|
||||
barData: CombinedData[];
|
||||
gap: number;
|
||||
roundedRadius: number;
|
||||
x: ScaleBandType;
|
||||
y: ScaleLinearType;
|
||||
bandwidth: number;
|
||||
barColors: string[];
|
||||
xValues: unknown[]; // TODO: update this value when this data type is defined
|
||||
barData: CombinedData[];
|
||||
gap: number;
|
||||
roundedRadius: number;
|
||||
}
|
||||
|
||||
export const CustomBars: React.FC<Partial<CustomBarsProps>> = ({
|
||||
x: scaleX,
|
||||
y: scaleY,
|
||||
bandwidth,
|
||||
barData,
|
||||
xValues,
|
||||
barColors,
|
||||
gap = 2,
|
||||
roundedRadius = 4
|
||||
x: scaleX,
|
||||
y: scaleY,
|
||||
bandwidth,
|
||||
barData,
|
||||
xValues,
|
||||
barColors,
|
||||
gap = 2,
|
||||
roundedRadius = 4,
|
||||
}) => {
|
||||
const barWidth = calculateBarWidth(bandwidth, barData.length, gap);
|
||||
const barWidth = calculateBarWidth(bandwidth, barData.length, gap);
|
||||
|
||||
return xValues.map((_, index) => (
|
||||
<Svg key={`group-${index}`} testID={`svg-${index}`}>
|
||||
{barData.map((item, i) => (
|
||||
<Bar
|
||||
key={`bar-${i}-${index}`}
|
||||
scaleX={scaleX}
|
||||
scaleY={scaleY}
|
||||
data={item.data[index]}
|
||||
barNumber={i}
|
||||
index={index}
|
||||
fill={barColors[i]}
|
||||
barWidth={barWidth}
|
||||
gap={gap}
|
||||
roundedRadius={roundedRadius}
|
||||
/>
|
||||
))}
|
||||
</Svg>
|
||||
));
|
||||
};
|
||||
return xValues.map((_, index) => (
|
||||
<Svg key={`group-${index}`} testID={`svg-${index}`}>
|
||||
{barData.map((item, i) => (
|
||||
<Bar
|
||||
key={`bar-${i}-${index}`}
|
||||
scaleX={scaleX}
|
||||
scaleY={scaleY}
|
||||
data={item.data[index]}
|
||||
barNumber={i}
|
||||
index={index}
|
||||
fill={barColors[i]}
|
||||
barWidth={barWidth}
|
||||
gap={gap}
|
||||
roundedRadius={roundedRadius}
|
||||
/>
|
||||
))}
|
||||
</Svg>
|
||||
));
|
||||
};
|
||||
|
@@ -1,20 +1,20 @@
|
||||
import React from 'react';
|
||||
import { G, Line } from 'react-native-svg';
|
||||
import { colors } from '../../styles';
|
||||
import { ScaleBandType, ScaleLinearType } from './graph-types';
|
||||
import React from "react";
|
||||
import { G, Line } from "react-native-svg";
|
||||
import { colors } from "../../styles";
|
||||
import { ScaleBandType, ScaleLinearType } from "./graph-types";
|
||||
|
||||
interface CustomGridProps {
|
||||
x: ScaleBandType;
|
||||
y: ScaleLinearType;
|
||||
ticks: Array<number>;
|
||||
includeVertical?: boolean;
|
||||
xTicks?: Array<number | string>;
|
||||
x: ScaleBandType;
|
||||
y: ScaleLinearType;
|
||||
ticks: Array<number>;
|
||||
includeVertical?: boolean;
|
||||
xTicks?: Array<number | string>;
|
||||
}
|
||||
/**
|
||||
* CustomGrid Component
|
||||
*
|
||||
*
|
||||
* This component is used within a react-native-svg-chart component to render grid lines.
|
||||
*
|
||||
*
|
||||
* @param {ScaleBandType} x - X-axis scale function, received from the parent chart component.
|
||||
* @param {ScaleLinearType} y - Y-axis scale function, received from the parent chart component.
|
||||
* @param {Array<number>} ticks - Y-axis tick values for horizontal lines, received from the parent.
|
||||
@@ -34,55 +34,74 @@ interface CustomGridProps {
|
||||
* Note: Use `includeVertical` cautiously; vertical lines are not fully developed.
|
||||
*/
|
||||
export const CustomGrid: React.FC<Partial<CustomGridProps>> = ({
|
||||
x,
|
||||
y,
|
||||
ticks,
|
||||
xTicks,
|
||||
includeVertical = false
|
||||
x,
|
||||
y,
|
||||
ticks,
|
||||
xTicks,
|
||||
includeVertical = false,
|
||||
}) => {
|
||||
const [firstTick, ...remainingTicks] = ticks;
|
||||
const dashArray = [1, 3];
|
||||
const strokeSolidWidth = 0.2;
|
||||
const strokeSolidColor = colors.bgBlack;
|
||||
const strokeDashWidth = 1;
|
||||
const strokeDashColor = colors.lightGrey;
|
||||
const [firstTick, ...remainingTicks] = ticks;
|
||||
const dashArray = [1, 3];
|
||||
const strokeSolidWidth = 0.2;
|
||||
const strokeSolidColor = colors.bgBlack;
|
||||
const strokeDashWidth = 1;
|
||||
const strokeDashColor = colors.lightGrey;
|
||||
|
||||
const renderHorizontalLine = (tick: number, stroke: string, strokeWidth: number, dashArray?: number[]) => (
|
||||
<Line
|
||||
key={`line-${tick}`}
|
||||
x1="0%"
|
||||
x2="100%"
|
||||
y1={y(tick)}
|
||||
y2={y(tick)}
|
||||
stroke={stroke}
|
||||
strokeWidth={strokeWidth}
|
||||
strokeDasharray={dashArray}
|
||||
/>
|
||||
);
|
||||
const renderHorizontalLine = (
|
||||
tick: number,
|
||||
stroke: string,
|
||||
strokeWidth: number,
|
||||
dashArray?: number[],
|
||||
) => (
|
||||
<Line
|
||||
key={`line-${tick}`}
|
||||
x1="0%"
|
||||
x2="100%"
|
||||
y1={y(tick)}
|
||||
y2={y(tick)}
|
||||
stroke={stroke}
|
||||
strokeWidth={strokeWidth}
|
||||
strokeDasharray={dashArray}
|
||||
/>
|
||||
);
|
||||
|
||||
const topY = y(Math.max(...ticks));
|
||||
const bottomY = y(Math.min(...ticks));
|
||||
const renderVerticalLine = (tick: number, stroke: string, strokeWidth: number, dashArray?: number[]) => {
|
||||
return (
|
||||
<Line
|
||||
key={`vertical-line-${tick}`}
|
||||
x1={x(tick)}
|
||||
x2={x(tick)}
|
||||
y1={topY}
|
||||
y2={bottomY}
|
||||
stroke={stroke}
|
||||
strokeWidth={strokeWidth}
|
||||
strokeDasharray={dashArray}
|
||||
/>
|
||||
);
|
||||
};
|
||||
const topY = y(Math.max(...ticks));
|
||||
const bottomY = y(Math.min(...ticks));
|
||||
const renderVerticalLine = (
|
||||
tick: number,
|
||||
stroke: string,
|
||||
strokeWidth: number,
|
||||
dashArray?: number[],
|
||||
) => {
|
||||
return (
|
||||
<Line
|
||||
key={`vertical-line-${tick}`}
|
||||
x1={x(tick)}
|
||||
x2={x(tick)}
|
||||
y1={topY}
|
||||
y2={bottomY}
|
||||
stroke={stroke}
|
||||
strokeWidth={strokeWidth}
|
||||
strokeDasharray={dashArray}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<G>
|
||||
{renderHorizontalLine(firstTick, strokeSolidColor, strokeSolidWidth)}
|
||||
{remainingTicks.map((tick) => renderHorizontalLine(tick, strokeDashColor, strokeDashWidth, dashArray))}
|
||||
{includeVertical && xTicks.map((_, index) => renderVerticalLine(index, strokeDashColor, strokeDashWidth, dashArray))}
|
||||
</G>
|
||||
);
|
||||
return (
|
||||
<G>
|
||||
{renderHorizontalLine(firstTick, strokeSolidColor, strokeSolidWidth)}
|
||||
{remainingTicks.map((tick) =>
|
||||
renderHorizontalLine(tick, strokeDashColor, strokeDashWidth, dashArray),
|
||||
)}
|
||||
{includeVertical &&
|
||||
xTicks.map((_, index) =>
|
||||
renderVerticalLine(
|
||||
index,
|
||||
strokeDashColor,
|
||||
strokeDashWidth,
|
||||
dashArray,
|
||||
),
|
||||
)}
|
||||
</G>
|
||||
);
|
||||
};
|
||||
|
@@ -1,13 +1,13 @@
|
||||
// TODO: Style values should be moved to styles
|
||||
// non-style config can live here as chartDefaults
|
||||
export const chartDefaults = {
|
||||
height: 300,
|
||||
spacingInner: 0.3,
|
||||
spacingOuter: 0.2,
|
||||
contentInset: { top: 30, bottom: 30 },
|
||||
numberOfTicks: 6,
|
||||
min: 0,
|
||||
barColors: ['#598EBB', '#F2D4BC', '#DB7878'],
|
||||
includeColors: true,
|
||||
lineStrokeWidth: 2
|
||||
};
|
||||
height: 300,
|
||||
spacingInner: 0.3,
|
||||
spacingOuter: 0.2,
|
||||
contentInset: { top: 30, bottom: 30 },
|
||||
numberOfTicks: 6,
|
||||
min: 0,
|
||||
barColors: ["#598EBB", "#F2D4BC", "#DB7878"],
|
||||
includeColors: true,
|
||||
lineStrokeWidth: 2,
|
||||
};
|
||||
|
@@ -1,54 +1,54 @@
|
||||
import { ScaleBand, ScaleLinear } from 'd3-scale'
|
||||
import { ScaleBand, ScaleLinear } from "d3-scale";
|
||||
|
||||
export type ScaleLinearType = ScaleLinear<number, number>;
|
||||
export type ScaleBandType = ScaleBand<number | string>;
|
||||
|
||||
export interface YAxisData {
|
||||
key: string; // string value for ChartLabel and useGraphData
|
||||
values: Array<number>;
|
||||
// including this code only for review --
|
||||
// do we prefer the idea of passing label formatting from the data or in the component?
|
||||
// generic function type, specific usage of value varies
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
formatLabel?: (value: number) => string;
|
||||
key: string; // string value for ChartLabel and useGraphData
|
||||
values: Array<number>;
|
||||
// including this code only for review --
|
||||
// do we prefer the idea of passing label formatting from the data or in the component?
|
||||
// generic function type, specific usage of value varies
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
formatLabel?: (value: number) => string;
|
||||
}
|
||||
|
||||
export type XValue = string | number
|
||||
export type XValue = string | number;
|
||||
|
||||
export interface GraphData {
|
||||
xValues: Array<XValue>;
|
||||
yValues: Array<YAxisData>;
|
||||
xValues: Array<XValue>;
|
||||
yValues: Array<YAxisData>;
|
||||
}
|
||||
|
||||
export interface YAxisProps {
|
||||
maxLeftYAxisValue?: number;
|
||||
maxRightYAxisValue?: number;
|
||||
selectedLeftYAxisLabel?: string;
|
||||
selectedRightYAxisLabel?: string;
|
||||
// generic function type, specific usage of value varies
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
formatRightYAxisLabel?: (value: string) => string;
|
||||
// generic function type, specific usage of value varies
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
formatLeftYAxisLabel?: (value: string) => string;
|
||||
maxLeftYAxisValue?: number;
|
||||
maxRightYAxisValue?: number;
|
||||
selectedLeftYAxisLabel?: string;
|
||||
selectedRightYAxisLabel?: string;
|
||||
// generic function type, specific usage of value varies
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
formatRightYAxisLabel?: (value: string) => string;
|
||||
// generic function type, specific usage of value varies
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
formatLeftYAxisLabel?: (value: string) => string;
|
||||
}
|
||||
export interface GraphProps {
|
||||
data: GraphData;
|
||||
includeColors?: boolean;
|
||||
height?: number;
|
||||
spacingInner?: number;
|
||||
spacingOuter?: number;
|
||||
contentInset?: { top: number; bottom: number };
|
||||
min?: number;
|
||||
numberOfTicks?: number;
|
||||
barColors?: Array<string>;
|
||||
lineStrokeWidth?: number;
|
||||
useCommonScale?: boolean;
|
||||
yAxisProps?: YAxisProps;
|
||||
data: GraphData;
|
||||
includeColors?: boolean;
|
||||
height?: number;
|
||||
spacingInner?: number;
|
||||
spacingOuter?: number;
|
||||
contentInset?: { top: number; bottom: number };
|
||||
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.
|
||||
@@ -63,16 +63,16 @@ export interface GraphProps {
|
||||
* @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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
@@ -1,43 +1,52 @@
|
||||
import { GraphData } from "./graph-types";
|
||||
|
||||
|
||||
export const convertToGraphData = (
|
||||
graphData: GraphData,
|
||||
options: {
|
||||
selectedLeftYAxisLabel?: string;
|
||||
selectedRightYAxisLabel?: string;
|
||||
maxRightYAxisValue: number;
|
||||
maxLeftYAxisValue: number;
|
||||
includeColors: boolean;
|
||||
barColors: Array<string>
|
||||
}
|
||||
graphData: GraphData,
|
||||
options: {
|
||||
selectedLeftYAxisLabel?: string;
|
||||
selectedRightYAxisLabel?: string;
|
||||
maxRightYAxisValue: number;
|
||||
maxLeftYAxisValue: number;
|
||||
includeColors: boolean;
|
||||
barColors: Array<string>;
|
||||
},
|
||||
) => {
|
||||
const xValues = graphData.xValues;
|
||||
const leftAxisIndex = graphData.yValues.findIndex(
|
||||
(y) => y.key === options.selectedLeftYAxisLabel
|
||||
);
|
||||
const rightAxisIndex = graphData.yValues.findIndex(
|
||||
(y) => y.key === options.selectedRightYAxisLabel
|
||||
);
|
||||
const xValues = graphData.xValues;
|
||||
const leftAxisIndex = graphData.yValues.findIndex(
|
||||
(y) => y.key === options.selectedLeftYAxisLabel,
|
||||
);
|
||||
const rightAxisIndex = graphData.yValues.findIndex(
|
||||
(y) => y.key === options.selectedRightYAxisLabel,
|
||||
);
|
||||
|
||||
// scale data points according to max value
|
||||
const yData = graphData.yValues.map((yAxis, index) => {
|
||||
const maxValue = index === rightAxisIndex ? options.maxRightYAxisValue : options.maxLeftYAxisValue;
|
||||
// scale data points according to max value
|
||||
const yData = graphData.yValues.map((yAxis, index) => {
|
||||
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);
|
||||
// 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 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 {
|
||||
data: mappedData,
|
||||
svg: { fill: 'transparent', stroke: strokeColor } // Apply the stroke color here
|
||||
};
|
||||
});
|
||||
const yAxisLeftLabels = leftAxisIndex !== -1 ? graphData.yValues[leftAxisIndex] : null;
|
||||
const yAxisRightLabels = rightAxisIndex !== -1 ? graphData.yValues[rightAxisIndex] : undefined;
|
||||
return {
|
||||
data: mappedData,
|
||||
svg: { fill: "transparent", stroke: strokeColor }, // Apply the stroke color here
|
||||
};
|
||||
});
|
||||
const yAxisLeftLabels =
|
||||
leftAxisIndex !== -1 ? graphData.yValues[leftAxisIndex] : null;
|
||||
const yAxisRightLabels =
|
||||
rightAxisIndex !== -1 ? graphData.yValues[rightAxisIndex] : undefined;
|
||||
|
||||
return { yData, yAxisLeftLabels, yAxisRightLabels, xValues };
|
||||
return { yData, yAxisLeftLabels, yAxisRightLabels, xValues };
|
||||
};
|
||||
|
@@ -1,90 +1,103 @@
|
||||
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 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';
|
||||
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);
|
||||
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>
|
||||
);
|
||||
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;
|
||||
|
@@ -1,57 +1,70 @@
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { convertToGraphData } from './graph-utils';
|
||||
import { chartDefaults } from './graph-config';
|
||||
import { GraphData, GraphProps, XValue, YAxisData } from './graph-types';
|
||||
|
||||
import { useMemo } from "react";
|
||||
|
||||
import { convertToGraphData } from "./graph-utils";
|
||||
import { chartDefaults } from "./graph-config";
|
||||
import { GraphData, GraphProps, XValue, YAxisData } from "./graph-types";
|
||||
|
||||
interface useGraphDataInterface {
|
||||
xValues: Array<XValue>;
|
||||
yData: {
|
||||
data: {
|
||||
value: number;
|
||||
}[];
|
||||
svg: {
|
||||
fill: string;
|
||||
};
|
||||
}[];
|
||||
yAxisLeftLabels: YAxisData;
|
||||
yAxisRightLabels: YAxisData;
|
||||
defaultProps: Partial<GraphProps>;
|
||||
xValues: Array<XValue>;
|
||||
yData: {
|
||||
data: {
|
||||
value: number;
|
||||
}[];
|
||||
svg: {
|
||||
fill: string;
|
||||
};
|
||||
}[];
|
||||
yAxisLeftLabels: YAxisData;
|
||||
yAxisRightLabels: YAxisData;
|
||||
defaultProps: Partial<GraphProps>;
|
||||
}
|
||||
|
||||
// this version assumes string values for X, this isn't necessarily the case
|
||||
// convertToGraphData is specifically tailored to bar/group bar graphs
|
||||
// ultimately this component could be used by any x & y axis graph types (line/bar/scatter)
|
||||
export const useGraphData = (graphData: GraphData, props: Partial<GraphProps>): useGraphDataInterface => {
|
||||
const { yAxisProps = {}, ...otherProps } = props;
|
||||
const defaultProps = {
|
||||
...chartDefaults,
|
||||
...otherProps,
|
||||
// assign default values for yAxisProps + spread to override with values coming from props
|
||||
yAxisProps: {
|
||||
maxLeftYAxisValue: Math.max(...(graphData.yValues[0]?.values ?? [0])),
|
||||
maxRightYAxisValue:
|
||||
graphData.yValues.length > 1 ? Math.max(...graphData.yValues[1]?.values) : undefined,
|
||||
formatRightYAxisLabel: yAxisProps.formatRightYAxisLabel,
|
||||
formatLeftYAxisLabel: yAxisProps.formatLeftYAxisLabel,
|
||||
selectedLeftYAxisLabel: graphData.yValues[0]?.key,
|
||||
selectedRightYAxisLabel: graphData.yValues[1]?.key,
|
||||
...yAxisProps
|
||||
}
|
||||
};
|
||||
export const useGraphData = (
|
||||
graphData: GraphData,
|
||||
props: Partial<GraphProps>,
|
||||
): useGraphDataInterface => {
|
||||
const { yAxisProps = {}, ...otherProps } = props;
|
||||
const defaultProps = {
|
||||
...chartDefaults,
|
||||
...otherProps,
|
||||
// assign default values for yAxisProps + spread to override with values coming from props
|
||||
yAxisProps: {
|
||||
maxLeftYAxisValue: Math.max(...(graphData.yValues[0]?.values ?? [0])),
|
||||
maxRightYAxisValue:
|
||||
graphData.yValues.length > 1
|
||||
? Math.max(...graphData.yValues[1]?.values)
|
||||
: undefined,
|
||||
formatRightYAxisLabel: yAxisProps.formatRightYAxisLabel,
|
||||
formatLeftYAxisLabel: yAxisProps.formatLeftYAxisLabel,
|
||||
selectedLeftYAxisLabel: graphData.yValues[0]?.key,
|
||||
selectedRightYAxisLabel: graphData.yValues[1]?.key,
|
||||
...yAxisProps,
|
||||
},
|
||||
};
|
||||
|
||||
const { yData, yAxisLeftLabels, yAxisRightLabels, xValues } = useMemo(
|
||||
() => convertToGraphData(graphData, { ...defaultProps.yAxisProps, includeColors: defaultProps.includeColors, barColors: defaultProps.barColors}),
|
||||
[graphData, defaultProps.yAxisProps, defaultProps.includeColors, defaultProps.barColors]
|
||||
);
|
||||
const { yData, yAxisLeftLabels, yAxisRightLabels, xValues } = useMemo(
|
||||
() =>
|
||||
convertToGraphData(graphData, {
|
||||
...defaultProps.yAxisProps,
|
||||
includeColors: defaultProps.includeColors,
|
||||
barColors: defaultProps.barColors,
|
||||
}),
|
||||
[
|
||||
graphData,
|
||||
defaultProps.yAxisProps,
|
||||
defaultProps.includeColors,
|
||||
defaultProps.barColors,
|
||||
],
|
||||
);
|
||||
|
||||
return {
|
||||
xValues,
|
||||
yData,
|
||||
yAxisLeftLabels,
|
||||
yAxisRightLabels,
|
||||
defaultProps
|
||||
};
|
||||
return {
|
||||
xValues,
|
||||
yData,
|
||||
yAxisLeftLabels,
|
||||
yAxisRightLabels,
|
||||
defaultProps,
|
||||
};
|
||||
};
|
||||
|
Reference in New Issue
Block a user