Newer
Older
import { View, FlatList } from 'react-native'
import range from 'date-range'
import { LocalDate } from 'js-joda'
import { makeYAxisLabels, normalizeToScale, makeHorizontalGrid } from './y-axis'
import nfpLines from './nfp-lines'
import { getCycleDay, getCycleDaysSortedByDate, getAmountOfCycleDays } from '../../db'
import { scaleObservable } from '../../local-storage'
import { AppText } from '../app-text'
import BleedingIcon from '../../assets/bleeding'
import CervixIcon from '../../assets/cervix'
import DesireIcon from '../../assets/desire'
import MucusIcon from '../../assets/mucus'
import NoteIcon from '../../assets/note'
import PainIcon from '../../assets/pain'
import SexIcon from '../../assets/sex'
constructor(props) {
super(props)
this.renderColumn = ({item, index}) => {
return (
<DayColumn
navigate={this.props.navigate}
symptomHeight={this.symptomHeight}
columnHeight={this.columnHeight}
chartHeight={this.state.chartHeight}
symptomRowSymptoms={this.symptomRowSymptoms}
this.cycleDaysSortedByDate = getCycleDaysSortedByDate()
onLayout = ({ nativeEvent }) => {
if (this.state.chartHeight) return
const height = nativeEvent.layout.height
this.setState({ chartHeight: height })
this.reCalculateChartInfo = () => {
// how many symptoms need to be displayed on the chart's upper symptom row?
this.symptomRowSymptoms = [
'bleeding',
'mucus',
'cervix',
'sex',
'desire',
'pain',
'note'
].filter((symptomName) => {
return this.cycleDaysSortedByDate.some(cycleDay => {
return cycleDay[symptomName]
})
})
this.xAxisHeight = this.state.chartHeight * config.xAxisHeightPercentage
const remainingHeight = this.state.chartHeight - this.xAxisHeight
this.symptomHeight = config.symptomHeightPercentage * remainingHeight
this.symptomRowHeight = this.symptomRowSymptoms.length * this.symptomHeight
this.columnHeight = remainingHeight - this.symptomRowHeight
const chartSymptoms = [...this.symptomRowSymptoms]
if (this.cycleDaysSortedByDate.some(day => day.temperature)) {
chartSymptoms.push('temperature')
}
const columnData = this.makeColumnInfo(nfpLines(), chartSymptoms)
this.setState({ columns: columnData })
this.cycleDaysSortedByDate.addListener(this.reCalculateChartInfo)
this.removeObvListener = scaleObservable(this.reCalculateChartInfo, false)
this.cycleDaysSortedByDate.removeListener(this.reCalculateChartInfo)
}
makeColumnInfo(getFhmAndLtlInfo, chartSymptoms) {
let amountOfCycleDays = getAmountOfCycleDays()
// if there's not much data yet, we want to show at least 30 days on the chart
if (amountOfCycleDays < 30) {
amountOfCycleDays = 30
} else {
// we don't want the chart to end abruptly before the first data day
amountOfCycleDays += 5
}
const jsDates = getTodayAndPreviousDays(amountOfCycleDays)
const xAxisDates = jsDates.map(jsDate => {
return LocalDate.of(
jsDate.getFullYear(),
jsDate.getMonth() + 1,
jsDate.getDate()
).toString()
})
const columns = xAxisDates.map(dateString => {
const column = { dateString }
const cycleDay = getCycleDay(dateString)
if (!cycleDay) return column
const symptoms = chartSymptoms.reduce((acc, symptom) => {
if (symptom === 'bleeding' ||
symptom === 'temperature' ||
symptom === 'mucus' ||
symptom === 'desire' ||
symptom === 'note'
) {
acc[symptom] = cycleDay[symptom] && cycleDay[symptom].value
acc.cervix = cycleDay.cervix &&
(cycleDay.cervix.opening + cycleDay.cervix.firmness)
} else if (symptom === 'sex') {
// solo = 1 + partner = 2
acc.sex = cycleDay.sex && (cycleDay.sex.solo + 2 * cycleDay.sex.partner)
} else if (symptom === 'pain') {
// is any pain documented?
acc.pain = cycleDay.pain &&
Object.values(cycleDay.pain).some(x => x === true)
acc[`${symptom}Exclude`] = cycleDay[symptom] && cycleDay[symptom].exclude
return acc
}, {})
const temp = symptoms.temperature
if (temp) {
column.y = normalizeToScale(temp, this.columnHeight)
const fhmAndLtl = getFhmAndLtlInfo(dateString, temp, this.columnHeight)
return Object.assign(column, symptoms, fhmAndLtl)
})
return columns.map((col, i) => {
const info = getInfoForNeighborColumns(i, columns)
return Object.assign(col, info)
})
}
<View
onLayout={this.onLayout}
style={{ flexDirection: 'row', flex: 1 }}
>
{!this.state.chartLoaded &&
<View style={{width: '100%', justifyContent: 'center', alignItems: 'center'}}>
</View>
}
{this.state.chartHeight && this.state.chartLoaded &&
<View style={[styles.yAxis, {height: this.symptomRowHeight}]}>
{this.symptomRowSymptoms.map(symptomName => {
return <View
style={{ alignItems: 'center', justifyContent: 'center' }}
key={symptomName}
width={styles.yAxis.width}
height={this.symptomRowHeight / this.symptomRowSymptoms.length}
>
<Svg
width={styles.yAxis.width * 0.8} height={this.symptomRowHeight / this.symptomRowSymptoms.length * 0.8}
viewBox={symptomIcons[symptomName].viewBox}
>
<G fill={symptomIcons[symptomName].color}>
{symptomIcons[symptomName].icon}
</G>
</Svg>
<View style={[styles.yAxis, {height: this.columnHeight}]}>
{makeYAxisLabels(this.columnHeight)}
</View>
<View style={[styles.yAxis, {height: this.xAxisHeight}]}>
<AppText style = {[styles.column.label.number, styles.yAxisLabels.cycleDayLabel]}>
{labels.cycleDayWithLinebreak}
</AppText>
<AppText style={[styles.column.label.date,styles.yAxisLabels.dateLabel]}>
{labels.date}
</AppText>
{this.state.chartHeight && this.state.chartLoaded &&
makeHorizontalGrid(this.columnHeight, this.symptomRowHeight)
}
{this.state.chartHeight &&
<FlatList
horizontal={true}
inverted={true}
showsHorizontalScrollIndicator={false}
data={this.state.columns}
renderItem={this.renderColumn}
keyExtractor={item => item.dateString}
initialNumToRender={15}
maxToRenderPerBatch={5}
onLayout={() => this.setState({chartLoaded: true})}
function getTodayAndPreviousDays(n) {
today.setHours(0)
today.setMinutes(0)
today.setSeconds(0)
today.setMilliseconds(0)
}
function getInfoForNeighborColumns(index, cols) {
const ret = {
rightY: null,
rightTemperatureExclude: null,
leftY: null,
leftTemperatureExclude: null
}
const right = index > 0 ? cols[index - 1] : undefined
const left = index < cols.length - 1 ? cols[index + 1] : undefined
if (right && right.y) {
ret.rightY = right.y
ret.rightTemperatureExclude = right.temperatureExclude
}
if (left && left.y) {
ret.leftY = left.y
ret.leftTemperatureExclude = left.temperatureExclude
}
return ret
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
}
const symptomIcons = {
'bleeding': {
'viewBox': '10 10 320 400',
'color': styles.iconShades.bleeding[3],
'icon': <BleedingIcon strokeWidth={'5'}/>
},
'mucus': {
'viewBox': '10 10 320 400',
'color': styles.iconShades.mucus[4],
'icon': <MucusIcon/>
},
'cervix': {
'viewBox': '10 10 320 440',
'color': styles.iconShades.cervix[3],
'icon': <CervixIcon/>
},
'desire': {
'viewBox': '10 10 320 380',
'color': styles.iconShades.desire[2],
'icon': <DesireIcon/>
},
'sex': {
'viewBox': '10 10 320 400',
'color': styles.iconShades.sex[2],
'icon': <SexIcon/>
},
'pain': {
'viewBox': '10 10 300 400',
'color': styles.iconShades.pain[0],
'icon': <PainIcon/>
},
'note': {
'viewBox': '10 10 270 400',
'color': styles.iconShades.note[0],
'icon': <NoteIcon/>
},