Skip to content

Instantly share code, notes, and snippets.

@ovieokeh
Created April 22, 2019 14:51
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ovieokeh/7daee6a1f737a19669758791fd7aa6c4 to your computer and use it in GitHub Desktop.
Save ovieokeh/7daee6a1f737a19669758791fd7aa6c4 to your computer and use it in GitHub Desktop.
A React.js component that parses data into a Bizcharts Line Graph
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Chart, Geom, Axis, Tooltip } from 'bizcharts';
import styled from 'styled-components';
import { themePurple, paleGrey, white, grey } from 'styles/colors';
import SecondaryDropdown from 'components/Dropdowns/SecondaryDropdown';
import { getActiveClients } from 'actions/clients';
import { remCalc } from 'styles/type';
interface iProps {
activeClients: any[];
getActiveClients: any;
}
interface iState {
activeClients: any[];
graphData: {};
filter: number;
previousMonths: any[];
}
const months = [
'Jan',
'Feb',
'Mar',
'Apr',
'May',
'Jun',
'Jul',
'Aug',
'Sep',
'Oct',
'Nov',
'Dec',
];
/**
* @description Displays the number of active clients
* per month for a specified period of time
*/
class ActiveClientsGraph extends Component<iProps, iState> {
constructor(props) {
super(props);
this.state = {
activeClients: [],
graphData: {},
filter: 3,
previousMonths: [],
};
}
async componentDidMount() {
await this.props.getActiveClients();
this.generatePreviousMonths();
this.updateGraphData();
}
static getDerivedStateFromProps(nextProps, prevState) {
return nextProps.activeClients !== prevState.activeClients
? { activeClients: nextProps.activeClients }
: null;
}
/**
* @description Updates the number of previous months
* to generate data for
*
* @param {number} month number of months to graph data for
* @returns {void}
*/
handleFilterChange = async months => {
const { getActiveClients } = this.props;
await this.setState({ filter: +months });
this.generatePreviousMonths();
switch (months) {
case '0':
await getActiveClients();
break;
default:
await getActiveClients(months);
}
};
/**
* @description Parses the raw data of the number
* of active clients, formats it into an
* acceptible data format for the chart component,
* and updates the state with the formatted data
*
* @returns {void}
*/
updateGraphData = async () => {
const { activeClients, previousMonths, filter } = this.state;
if (!activeClients) return false;
let monthData = activeClients.map(
client => months[new Date(client.updated_at).getMonth()],
);
const yearData = activeClients.map(client =>
new Date(client.updated_at).getFullYear(),
);
if (filter === 1) {
monthData = monthData.filter(
month => month === months[new Date().getMonth()],
);
}
const formattedMonths = {};
const formattedYears = {};
monthData.map(month => {
formattedMonths[month] = !formattedMonths[month]
? 1
: formattedMonths[month] + 1;
});
yearData.map(year => {
formattedYears[year] = !formattedYears[year]
? 1
: formattedYears[year] + 1;
});
let graphData;
if (filter === 0) {
graphData = Object.keys(formattedYears).map(year => ({
period: year,
active: formattedYears[year],
}));
} else {
graphData = Object.keys(formattedMonths).map(month => ({
period: month,
active: formattedMonths[month],
}));
}
previousMonths.forEach(month => {
if (!monthData.includes(month)) {
graphData.push({ period: month, active: 0 });
}
});
graphData.reverse();
this.setState({ graphData });
};
/**
* @description Generates the list of
* months since the current month, sets the state,
* and updates the graph
*
* @returns {void}
*/
generatePreviousMonths = async () => {
const { filter } = this.state;
if (filter === 1) {
await this.setState({ previousMonths: [] });
this.updateGraphData();
return;
}
const currentMonth = months[new Date().getMonth()];
const monthIndex = months.indexOf(currentMonth);
const since = monthIndex - filter;
let sliced;
since < 1
? (sliced = months.slice(0, monthIndex + 1))
: (sliced = months.slice(since, monthIndex + 1));
if (since < 1) {
const clonedMonths = [...months];
const reversedMonths = clonedMonths.reverse();
const leftoverCount = filter - sliced.length + 1;
const remainder = reversedMonths.slice(0, leftoverCount).reverse();
sliced = [...remainder, ...sliced].reverse();
}
await this.setState({ previousMonths: sliced });
this.updateGraphData();
};
render() {
const { graphData, activeClients } = this.state;
const cols = {
active: { min: 0 },
period: { range: [0, 1] },
};
const filters = [
{ label: 'This Month', value: 1 },
{ label: 'Last 3 Months', value: 3 },
{ label: 'Last 6 Months', value: 6 },
{ label: 'Last Year', value: 12 },
{ label: 'All Time', value: 0 },
];
return (
<Container>
<CardTitle>Active Clients</CardTitle>
<Border>
<GraphHeader>
<span>Number of Active Clients Since: </span>
<SecondaryDropdown
defaultOption="Last 3 Months"
options={filters}
handleOptionClick={this.handleFilterChange}
/>
</GraphHeader>
{activeClients.length ? (
<Chart
padding={50}
height={300}
data={graphData}
scale={cols}
placeholder="No data"
forceFit
>
<Axis name="period" />
<Axis name="active" />
<Tooltip crosshairs={{ type: 'y' }} />
<Geom
type="line"
position="period*active"
color={themePurple}
size={2}
/>
<Geom
type="point"
position="period*active"
size={4}
shape={'circle'}
color={themePurple}
/>
</Chart>
) : (
<p>No Clients Yet</p>
)}
</Border>
</Container>
);
}
}
const Container = styled.div`
background-color: ${white};
box-shadow: 0 ${remCalc(1)} ${remCalc(5)} ${remCalc(1)} rgba(0, 0, 0, 0.15);
border-radius: ${remCalc(5)};
text-align: left;
overflow: hidden;
padding: 0 ${remCalc(30)} ${remCalc(30)} ${remCalc(30)};
`;
const CardTitle = styled.p`
text-transform: uppercase;
color: ${grey};
`;
const Border = styled.div`
border: ${remCalc(1)} solid rgba(0, 0, 0, 0.15);
border-radius: ${remCalc(5)};
overflow: hidden;
text-align: center;
min-height: ${remCalc(300)};
`;
const GraphHeader = styled.div`
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
padding: 0.5em 0 0.5em 1em;
background-color: ${paleGrey};
`;
const mapStateToProps = ({ dashboard }) => ({
activeClients: dashboard.activeClients,
});
const mapDispatchToProps = dispatch => ({
getActiveClients: previousMonths =>
dispatch(getActiveClients(previousMonths)),
});
const ConnectedActiveClientsGraph = connect(
mapStateToProps,
mapDispatchToProps,
)(ActiveClientsGraph);
export default ConnectedActiveClientsGraph;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment