I have this DTO object generated from Rest API using Typescript:
export interface BillingSummaryDTO {
paid?: number,
outstanding?: number,
pastDue?: number,
cancelled?: number,
createdAt?: Moment | null,
}
export async function getBillingSummary(): Promise<AxiosResponse<BillingSummaryDTO[]>> {
return await axios.get<BillingSummaryDTO[]>(
`${baseUrl}/management/billing/summary`
);
}
Example chart:
import {
BarChart,
Bar,
XAxis,
YAxis,
CartesianGrid,
Tooltip,
Legend,
} from "recharts";
import {Box} from "@material-ui/core";
const data = [
{
name: "Jan",
Chargebacks: 4000,
Transactions: 2400,
USD: 2400,
},
{
name: "Feb",
Chargebacks: 3000,
Transactions: 1398,
USD: 2210,
},
{
name: "Mar",
Chargebacks: 2000,
Transactions: 9800,
USD: 2290,
},
{
name: "Apr",
Chargebacks: 2780,
Transactions: 3908,
USD: 2000,
},
{
name: "May",
Chargebacks: 1890,
Transactions: 4800,
USD: 2181,
},
{
name: "Jun",
Chargebacks: 2390,
Transactions: 3800,
USD: 2500,
},
{
name: "Jul",
Chargebacks: 3490,
Transactions: 4300,
USD: 2100,
},
{
name: "Aug",
Chargebacks: 3490,
Transactions: 4300,
USD: 2100,
},
{
name: "Sep",
Chargebacks: 3490,
Transactions: 4300,
USD: 2100,
},
];
const useStyles = makeStyles((theme) =>
createStyles({
root: {
flexGrow: 1,
},
paper: {
padding: theme.spacing(2),
textAlign: "center",
color: theme.palette.text.secondary,
},
})
);
const usePaperStyles = makeStyles((theme) =>
createStyles({
root: {
display: "flex",
flexWrap: "wrap",
"& > *": {
margin: theme.spacing(1),
width: theme.spacing(16),
height: theme.spacing(16),
},
},
})
);
const useTimelineStyles = makeStyles((theme) => ({
paper: {
padding: "6px 16px",
},
secondaryTail: {
backgroundColor: theme.palette.secondary.main,
},
}));
export default function Billing() {
const [click, setClick] = useState(false);
const closeMobileMenu = () => setClick(false);
const classes = useStyles();
const classesPaper = usePaperStyles();
const classesTimeline = useTimelineStyles();
return (
<>
<Grid container justifyContent="center" alignItems="center">
<Grid item xs={11}>
{/*Padding on the top*/}
<Box m="5rem" />
</Grid>
<Grid item xs={12} >
<h4 className="chart-label">Invoices Summary</h4>
<BarChart
width={1000}
height={300}
data={data}
margin={{
top: 5,
right: 30,
left: 20,
bottom: 5,
}}
barSize={30}
>
<XAxis
dataKey="name"
scale="point"
padding={{ left: 10, right: 10 }}
/>
<YAxis />
<Tooltip />
<Legend />
<CartesianGrid strokeDasharray="3 3" />
<Bar
dataKey="Transactions"
fill="#8884d8"
background={{ fill: "#eee" }}
/>
</BarChart>
</Grid>
</Grid>
</>
);
}
How I can use data from BillingSummaryDTO[]
to generate the chart?
Making a function that maps over BillingSummaryDTO[] and converts it into the data model (as visible in the variable data
) that you have specified for consumption by the <BarChart/>
is a valid approach.
export interface BarChartDataModel {
name: string,
Chargebacks: number,
Transactions: number,
USD: number,
}
const data : BarChartDataModel []
This data will be used in <BarChart/>
as
<BarChart
data={data}
// ....
>
// ...
</BarChart>
Such a function will follow the map
reduce
approach with a little help from a JS date management library like momentjs
4 step solution
BarChartDataModel
interface. A collection of objects following the BarChartDataModel
interface is the data
we pass to our <BarChart/>
component<BarChart/>
componentYour code for the same would be :
export interface BillingSummaryDTO {
paid?: number,
outstanding?: number,
pastDue?: number,
cancelled?: number,
createdAt?: Moment | null,
}
export interface BarChartDataModel {
name: string,
Chargebacks: number,
Transactions: number,
USD: number,
}
export async function getBillingSummary(): Promise<AxiosResponse<BillingSummaryDTO[]>> {
const response = await axios.get<BillingSummaryDTO[]>(
`${baseUrl}/management/billing/summary`
);
// STEP 1 : Chronological ordering. Oldest bills will show first
const chronologicallyOrdered = response.sort((a:BillingSummaryDTO,b:BillingSummaryDTO)=> a.createdAt - b.createdAt )
// STEP 2 : Bucket by year
const groupByYear = chronologicallyOrdered.reduce((yearwiseBills : any, bill:BillingSummaryDTO, currIdx:number)) =>
{
const year = moment(bill.createdAt).year().toString()
if(!yearwiseBills[year]){
yearwiseBills[year] = []
}
yearwiseBills[year].push(bill)
return yearwiseBills
}
,{})
// STEP 3 : In each yearwise bucket -> bucket by month
const groupByMonth = Object.keys(groupByYear).map((year, yearwiseBills) => yearwiseBills.reduce((monthwiseBills: any, bill:BillingSummaryDTO, currIdx:number)) =>
{
const moment = moment(bill.createdAt).month().toString()
if(!yearAcc[month]){
monthwiseBills[month] = []
}
monthwiseBills[month].push(bill)
return monthwiseBills
}
,{}) );
// STEP 4 : Reduce all bills in a monthwise bucket into a monthlyReport object and store all monthlyReport objects in an monthlyReportArray
const monthlyReportArray:BarChartDataModel[] = Object.keys(groupByMonth).map((year, yearwiseBills) =>
Object.keys(bills).map((month, monthwiseBills) => monthwiseBills.reduce((monthlyReport:BarChartDataModel,bill:BillingSummaryDTO) => {
if(bill.cancelled){
monthlyReport.Chargebacks++
}else{
monthlyReport.Transactions++,
monthlyReport.USD += bill.paid
}
return monthlyReport
},{
name : moment(month, 'M').format('MMM')
Transactions : 0,
USD : 0,
Chargebacks:0
} )
)
// STEP 5 : Consume this as the "data" for the "<BarChart/>" component
return monthlyReportArray
}
I bucketed by year first and then bucketed by month instead of directly bucketing by month because we don't want to combine the monthly reports of say "May of 1997" and "May of 1998" into just "May" for our chart. We would want them to be separate