Created
April 22, 2019 14:51
-
-
Save ovieokeh/7daee6a1f737a19669758791fd7aa6c4 to your computer and use it in GitHub Desktop.
A React.js component that parses data into a Bizcharts Line Graph
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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