Skip to content
Snippets Groups Projects
Commit f2b8723f authored by tina's avatar tina
Browse files

resolves some of the suggested improvements, renames the term period with cycle

parent c99aa750
No related branches found
No related tags found
No related merge requests found
import React, { Component } from 'react' import React, { Component } from 'react'
import { import {
Text, Text,
View,
ScrollView ScrollView
} from 'react-native' } from 'react-native'
import { LocalDate, ChronoUnit } from 'js-joda' import { LocalDate, ChronoUnit } from 'js-joda'
import styles from '../styles/index' import styles from '../styles/index'
import cycleModule from '../lib/cycle' import cycleModule from '../lib/cycle'
import getPeriodInfo from '../lib/period-length' import getCycleInfo from '../lib/cycle-length'
export default class Stats extends Component { export default class Stats extends Component {
constructor(props) {
super(props)
const allMensesStarts = cycleModule().getAllMensesStarts()
this.test = allMensesStarts
this.state = {
text: determineStatsText(allMensesStarts)
}
}
render() { render() {
const allMensesStarts = cycleModule().getAllMensesStarts()
const statsText = determineStatsText(allMensesStarts)
return ( return (
<ScrollView> <ScrollView>
<Text style={styles.welcome}>{this.state.text}</Text> <View>
<Text style={styles.stats}>{statsText}</Text>
</View>
</ScrollView> </ScrollView>
) )
} }
} }
function getCycleLength(cycleStartDates) { function getCycleLength(cycleStartDates) {
const periodLengths = [] const cycleLengths = []
for (let i = 0; i < cycleStartDates.length - 1; i++) { for (let i = 0; i < cycleStartDates.length - 1; i++) {
const nextPeriodStart = LocalDate.parse(cycleStartDates[i]) const nextCycleStart = LocalDate.parse(cycleStartDates[i])
const periodStart = LocalDate.parse(cycleStartDates[i + 1]) const cycleStart = LocalDate.parse(cycleStartDates[i + 1])
periodLengths.push(periodStart.until(nextPeriodStart, ChronoUnit.DAYS)) cycleLengths.push(cycleStart.until(nextCycleStart, ChronoUnit.DAYS))
} }
return periodLengths return cycleLengths
} }
function determineStatsText(allMensesStarts) { function determineStatsText(allMensesStarts) {
const emptyStats = 'At least one completed period is needed to present you with stats here.' const emptyStats = 'At least one completed cycle is needed to present you with stats here.'
if (allMensesStarts.length < 2) { if (allMensesStarts.length < 2) {
return emptyStats return emptyStats
} else { } else {
const cycleLengths = getCycleLength(allMensesStarts) const cycleLengths = getCycleLength(allMensesStarts)
const numberOfCycles = cycleLengths.length const numberOfCycles = cycleLengths.length
const periodInfo = getPeriodInfo(cycleLengths)
if (numberOfCycles === 1) { if (numberOfCycles === 1) {
return `You have documented one period of ${cycleLengths[0]} days.` return `You have documented one cycle of ${cycleLengths[0]} days.`
} else {
const statsText = `Stats are based on ${numberOfCycles} completed
periods.\n\n
Average period length: ${periodInfo.mean} days\n\n
shortest period: ${periodInfo.minimum} days\n
longest period: ${periodInfo.maximum} days\n
median length (meaning 50% of periods are of this length or shorter):
${periodInfo.median} days\n
standard deviation: ${periodInfo.stdDeviation}`
return statsText
} }
const cycleInfo = getCycleInfo(cycleLengths)
const statsText = `Stats are based on ${numberOfCycles} completed cycles.\n\n\
Average cycle length: ${cycleInfo.mean} days\n\nShortest cycle: ${cycleInfo.minimum} days\nLongest cycle: ${cycleInfo.maximum} days\nMedian length (meaning 50% of cycles are of this length or shorter): ${cycleInfo.median} days\nStandard deviation: ${cycleInfo.stdDeviation}`
return statsText
} }
} }
\ No newline at end of file
import assert from 'assert' import assert from 'assert'
export default function getPeriodLengthStats(cycleLengths) { export default function getCycleLengthStats(cycleLengths) {
throwIfArgsAreNotInRequiredFormat(cycleLengths) throwIfArgsAreNotInRequiredFormat(cycleLengths)
const periodLengthStats = {} const cycleLengthStats = {}
const sortedCycleLengths = cycleLengths.sort((a, b) => { const sortedCycleLengths = cycleLengths.sort((a, b) => {
return a - b return a - b
}) })
periodLengthStats.minimum = sortedCycleLengths[0] cycleLengthStats.minimum = sortedCycleLengths[0]
periodLengthStats.maximum = sortedCycleLengths[cycleLengths.length - 1] cycleLengthStats.maximum = sortedCycleLengths[cycleLengths.length - 1]
periodLengthStats.mean = Math.round( cycleLengthStats.mean = Math.round(
cycleLengths.reduce(getSum) / cycleLengths.length * 100 cycleLengths.reduce(getSum) / cycleLengths.length * 100
) / 100 ) / 100
// median // median
if (cycleLengths.length % 2 == 1) { if (cycleLengths.length % 2 == 1) {
periodLengthStats.median = sortedCycleLengths[ cycleLengthStats.median = sortedCycleLengths[
(cycleLengths.length + 1) / 2 - 1 (cycleLengths.length + 1) / 2 - 1
] ]
} else { } else {
const middle = cycleLengths.length / 2 const middle = cycleLengths.length / 2
periodLengthStats.median = (sortedCycleLengths[middle - 1] + cycleLengthStats.median = (sortedCycleLengths[middle - 1] +
sortedCycleLengths[middle]) / 2 sortedCycleLengths[middle]) / 2
} }
// corrected standard deviation (based on unbiased sample variance) // corrected standard deviation (based on unbiased sample variance)
if (cycleLengths.length > 1) { if (cycleLengths.length > 1) {
const sumOfSquares = cycleLengths.map(cycleLength => { const sumOfSquares = cycleLengths.map(cycleLength => {
return Math.pow(cycleLength - periodLengthStats.mean, 2) return Math.pow(cycleLength - cycleLengthStats.mean, 2)
}).reduce(getSum) }).reduce(getSum)
periodLengthStats.stdDeviation = Math.round( cycleLengthStats.stdDeviation = Math.round(
Math.sqrt(sumOfSquares / (cycleLengths.length - 1 )) * 100 Math.sqrt(sumOfSquares / (cycleLengths.length - 1 )) * 100
) / 100 ) / 100
} else { } else {
periodLengthStats.stdDeviation = null cycleLengthStats.stdDeviation = null
} }
return periodLengthStats return cycleLengthStats
} }
function getSum(total, num) { function getSum(total, num) {
......
...@@ -82,5 +82,11 @@ export default StyleSheet.create({ ...@@ -82,5 +82,11 @@ export default StyleSheet.create({
marginTop: 15, marginTop: 15,
marginLeft: 'auto', marginLeft: 'auto',
marginRight: 'auto' marginRight: 'auto'
},
stats: {
fontSize: 18,
margin: 30,
textAlign: 'left',
textAlignVertical: 'center'
} }
}) })
\ No newline at end of file
import chai from 'chai' import chai from 'chai'
import { AssertionError } from 'assert' import { AssertionError } from 'assert'
import periodInfo from '../lib/period-length' import cycleInfo from '../lib/cycle-length'
const expect = chai.expect const expect = chai.expect
describe('getPeriodLengthStats', () => { describe('getCycleLengthStats', () => {
it('works for a simple odd-numbered array', () => { it('works for a simple odd-numbered array', () => {
const periodLengths = [99, 5, 1, 2, 100] const cycleLengths = [99, 5, 1, 2, 100]
const result = periodInfo(periodLengths) const result = cycleInfo(cycleLengths)
const expectedResult = { const expectedResult = {
minimum: 1, minimum: 1,
maximum: 100, maximum: 100,
...@@ -20,8 +20,8 @@ describe('getPeriodLengthStats', () => { ...@@ -20,8 +20,8 @@ describe('getPeriodLengthStats', () => {
}) })
it('works for a simple even-numbered array', () => { it('works for a simple even-numbered array', () => {
const periodLengths = [4, 1, 15, 2, 20, 5] const cycleLengths = [4, 1, 15, 2, 20, 5]
const result = periodInfo(periodLengths) const result = cycleInfo(cycleLengths)
const expectedResult = { const expectedResult = {
minimum: 1, minimum: 1,
maximum: 20, maximum: 20,
...@@ -32,8 +32,8 @@ describe('getPeriodLengthStats', () => { ...@@ -32,8 +32,8 @@ describe('getPeriodLengthStats', () => {
expect(result).to.eql(expectedResult) expect(result).to.eql(expectedResult)
}) })
it('works for an one-element array', () => { it('works for an one-element array', () => {
const periodLengths = [42] const cycleLengths = [42]
const result = periodInfo(periodLengths) const result = cycleInfo(cycleLengths)
const expectedResult = { const expectedResult = {
minimum: 42, minimum: 42,
maximum: 42, maximum: 42,
...@@ -45,20 +45,20 @@ describe('getPeriodLengthStats', () => { ...@@ -45,20 +45,20 @@ describe('getPeriodLengthStats', () => {
}) })
describe('when args are wrong', () => { describe('when args are wrong', () => {
it('throws when arg object is an empty array', () => { it('throws when arg object is an empty array', () => {
const periodLengths = [] const cycleLengths = []
expect(() => periodInfo(periodLengths).to.throw(AssertionError)) expect(() => cycleInfo(cycleLengths).to.throw(AssertionError))
}) })
it('throws when arg object is not in right format', () => { it('throws when arg object is not in right format', () => {
const wrongObject = { hello: 'world' } const wrongObject = { hello: 'world' }
expect(() => periodInfo(wrongObject).to.throw(AssertionError)) expect(() => cycleInfo(wrongObject).to.throw(AssertionError))
}) })
it('throws when arg array contains a string', () => { it('throws when arg array contains a string', () => {
const wrongElement = [4, 1, 15, '2', 20, 5] const wrongElement = [4, 1, 15, '2', 20, 5]
expect(() => periodInfo(wrongElement).to.throw(AssertionError)) expect(() => cycleInfo(wrongElement).to.throw(AssertionError))
}) })
it('throws when arg array contains a NaN', () => { it('throws when arg array contains a NaN', () => {
const wrongElement = [4, 1, 15, NaN, 20, 5] const wrongElement = [4, 1, 15, NaN, 20, 5]
expect(() => periodInfo(wrongElement).to.throw(AssertionError)) expect(() => cycleInfo(wrongElement).to.throw(AssertionError))
}) })
}) })
}) })
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment