diff --git a/components/chart/chart.js b/components/chart/chart.js index 7a90b87..49ddaa1 100644 --- a/components/chart/chart.js +++ b/components/chart/chart.js @@ -1,24 +1,14 @@ import React, { Component } from 'react' -import { Text as ReactNativeText, View, FlatList } from 'react-native' +import { View, FlatList } from 'react-native' import range from 'date-range' -import Svg,{ - G, - Rect, - Text, - Circle, - Line, - Path -} from 'react-native-svg' import { LocalDate } from 'js-joda' -import { getCycleDay, getOrCreateCycleDay, cycleDaysSortedByDate } from '../../db' -import cycleModule from '../../lib/cycle' +import { yAxis, normalizeToScale } from './y-axis' +import DayColumn from './day-column' +import { getCycleDay, cycleDaysSortedByDate } from '../../db' import styles from './styles' import config from './config' -import { getCycleStatusForDay } from '../../lib/sympto-adapter' -const getCycleDayNumber = cycleModule().getCycleDayNumber - -const yAxis = makeYAxis(config) +const yAxisView = {yAxis.labels} export default class CycleChart extends Component { constructor(props) { @@ -40,15 +30,11 @@ export default class CycleChart extends Component { cycleDaysSortedByDate.removeListener(this.reCalculateChartInfo) } - passDateToDayView(dateString) { - const cycleDay = getOrCreateCycleDay(dateString) - this.props.navigation.navigate('cycleDay', { cycleDay }) - } render() { return ( - {yAxis.labels} + { yAxisView } 0 ? cols[index - 1] : undefined } leftNeighbor = {index < cols.length - 1 ? cols[index + 1] : undefined } + navigate={this.props.navigation.navigate} /> ) }} keyExtractor={item => item.dateString} - initialNumToRender={20} + initialNumToRender={15} > @@ -74,137 +61,6 @@ export default class CycleChart extends Component { } } -class DayColumn extends Component { - makeDayColumn({ dateString, cycleDay, y }, index) { - const cycleDayNumber = getCycleDayNumber(dateString) - const label = styles.column.label - const dateLabel = dateString.split('-').slice(1).join('-') - const getFhmAndLtlInfo = setUpFertilityStatusFunc() - const nfpLineInfo = getFhmAndLtlInfo(dateString, cycleDay) - - return ( - this.passDateToDayView(dateString)}> - - {nfpLineInfo.drawFhmLine ? - : null} - - {this.placeHorizontalGrid()} - - - {cycleDayNumber} - - - {dateLabel} - - - {cycleDay && cycleDay.bleeding ? - - : null} - - {nfpLineInfo.drawLtlAt ? - : null} - - {y ? - this.drawDotAndLines(y, cycleDay.temperature.exclude, index) - : null - } - {cycleDay && cycleDay.mucus ? - : null} - - {y ? - this.drawDotAndLines(y, cycleDay.temperature.exclude) - : null} - - ) - } - - drawDotAndLines(currY, exclude) { - let lineToRight - let lineToLeft - - function makeLine(otherColY, x, excludeLine) { - const middleY = ((otherColY - currY) / 2) + currY - const target = [x, middleY] - const lineStyle = excludeLine ? styles.curveExcluded : styles.curve - - return - } - - const thereIsADotToTheRight = this.props.rightNeighbor && this.props.rightNeighbor.y - const thereIsADotToTheLeft = this.props.leftNeighbor && this.props.leftNeighbor.y - - if (thereIsADotToTheRight) { - const neighbor = this.props.rightNeighbor - const excludedLine = neighbor.cycleDay.temperature.exclude || exclude - lineToRight = makeLine(neighbor.y, config.columnWidth, excludedLine) - } - if (thereIsADotToTheLeft) { - const neighbor = this.props.leftNeighbor - const excludedLine = neighbor.cycleDay.temperature.exclude || exclude - lineToLeft = makeLine(neighbor.y, 0, excludedLine) - } - - const dotStyle = exclude ? styles.curveDotsExcluded : styles.curveDots - return ( - {lineToRight} - {lineToLeft} - - ) - } - - placeHorizontalGrid() { - return yAxis.tickPositions.map(tick => { - return ( - - ) - }) - } - - render() { - return ( - - {this.makeDayColumn(this.props.item, this.props.index)} - - ) - } -} - function makeColumnInfo(n) { const xAxisDates = getPreviousDays(n).map(jsDate => { return LocalDate.of( @@ -234,116 +90,4 @@ function getPreviousDays(n) { const earlierDate = new Date(today - (range.DAY * n)) return range(earlierDate, today).reverse() -} - -function normalizeToScale(temp) { - const scale = config.temperatureScale - const valueRelativeToScale = (scale.high - temp) / (scale.high - scale.low) - const scaleHeight = config.chartHeight - return scaleHeight * valueRelativeToScale -} - -function makeYAxis() { - const scaleMin = config.temperatureScale.low - const scaleMax = config.temperatureScale.high - const numberOfTicks = (scaleMax - scaleMin) * 2 - const tickDistance = config.chartHeight / numberOfTicks - - const tickPositions = [] - const labels = [] - // for style reasons, we don't want the first and last tick - for (let i = 1; i < numberOfTicks - 1; i++) { - const y = tickDistance * i - const style = styles.yAxisLabel - // this eyeballing is sadly necessary because RN does not - // support percentage values for transforms, which we'd need - // to reliably place the label vertically centered to the grid - style.top = y - 8 - labels.push( - - {scaleMax - i * 0.5} - - ) - tickPositions.push(y) - } - - return {labels, tickPositions} -} - -function setUpFertilityStatusFunc() { - let cycleStatus - let cycleStartDate - let noMoreCycles = false - - function updateCurrentCycle(dateString) { - cycleStatus = getCycleStatusForDay(dateString) - if(!cycleStatus) { - noMoreCycles = true - return - } - if (cycleStatus.phases.preOvulatory) { - cycleStartDate = cycleStatus.phases.preOvulatory.start.date - } else { - cycleStartDate = cycleStatus.phases.periOvulatory.start.date - } - } - - function dateIsInPeriOrPostPhase(dateString) { - return ( - dateString >= cycleStatus.phases.periOvulatory.start.date - ) - } - - function precededByAnotherTempValue(dateString) { - return ( - // we are only interested in days that have a preceding - // temp - Object.keys(cycleStatus.phases).some(phaseName => { - return cycleStatus.phases[phaseName].cycleDays.some(day => { - return day.temperature && day.date < dateString - }) - }) - // and also a following temp, so we don't draw the line - // longer than necessary - && - cycleStatus.phases.postOvulatory.cycleDays.some(day => { - return day.temperature && day.date > dateString - }) - ) - } - - function isInTempMeasuringPhase(cycleDay, dateString) { - return ( - cycleDay && cycleDay.temperature - || precededByAnotherTempValue(dateString) - ) - } - - return function(dateString, cycleDay) { - const ret = {} - if (!cycleStatus && !noMoreCycles) updateCurrentCycle(dateString) - if (noMoreCycles) return ret - - if (dateString < cycleStartDate) updateCurrentCycle(dateString) - if (noMoreCycles) return ret - - const tempShift = cycleStatus.temperatureShift - - if (tempShift) { - if (tempShift.firstHighMeasurementDay.date === dateString) { - ret.drawFhmLine = true - } - - if ( - dateIsInPeriOrPostPhase(dateString) && - isInTempMeasuringPhase(cycleDay, dateString) - ) { - ret.drawLtlAt = normalizeToScale(tempShift.ltl) - } - } - - return ret - } } \ No newline at end of file diff --git a/components/chart/day-column.js b/components/chart/day-column.js new file mode 100644 index 0000000..1b9b2f1 --- /dev/null +++ b/components/chart/day-column.js @@ -0,0 +1,145 @@ +import React, { Component } from 'react' +import Svg,{ + G, + Rect, + Text, + Circle, + Line, + Path +} from 'react-native-svg' +import styles from './styles' +import config from './config' +import { getOrCreateCycleDay } from '../../db' +import cycleModule from '../../lib/cycle' +import setUpFertilityStatusFunc from './nfp-lines' +import { horizontalGrid } from './y-axis' + +const getCycleDayNumber = cycleModule().getCycleDayNumber + +export default class DayColumn extends Component { + makeDayColumn({ dateString, cycleDay, y }, index) { + const cycleDayNumber = getCycleDayNumber(dateString) + const label = styles.column.label + const dateLabel = dateString.split('-').slice(1).join('-') + const getFhmAndLtlInfo = setUpFertilityStatusFunc() + const nfpLineInfo = getFhmAndLtlInfo(dateString, cycleDay) + + return ( + this.passDateToDayView(dateString)}> + + {horizontalGrid} + {nfpLineInfo.drawFhmLine ? + : null} + + + + {cycleDayNumber} + + + {dateLabel} + + + {cycleDay && cycleDay.bleeding ? + + : null} + + {nfpLineInfo.drawLtlAt ? + : null} + + {y ? + this.drawDotAndLines(y, cycleDay.temperature.exclude, index) + : null + } + {cycleDay && cycleDay.mucus ? + : null} + + {y ? + this.drawDotAndLines(y, cycleDay.temperature.exclude) + : null} + + ) + } + + drawDotAndLines(currY, exclude) { + let lineToRight + let lineToLeft + + function makeLine(otherColY, x, excludeLine) { + const middleY = ((otherColY - currY) / 2) + currY + const target = [x, middleY] + const lineStyle = excludeLine ? styles.curveExcluded : styles.curve + + return + } + + const thereIsADotToTheRight = this.props.rightNeighbor && this.props.rightNeighbor.y + const thereIsADotToTheLeft = this.props.leftNeighbor && this.props.leftNeighbor.y + + if (thereIsADotToTheRight) { + const neighbor = this.props.rightNeighbor + const excludedLine = neighbor.cycleDay.temperature.exclude || exclude + lineToRight = makeLine(neighbor.y, config.columnWidth, excludedLine) + } + if (thereIsADotToTheLeft) { + const neighbor = this.props.leftNeighbor + const excludedLine = neighbor.cycleDay.temperature.exclude || exclude + lineToLeft = makeLine(neighbor.y, 0, excludedLine) + } + + const dotStyle = exclude ? styles.curveDotsExcluded : styles.curveDots + return ( + {lineToRight} + {lineToLeft} + + ) + } + + + passDateToDayView(dateString) { + const cycleDay = getOrCreateCycleDay(dateString) + this.props.navigate('cycleDay', { cycleDay }) + } + + shouldComponentUpdate() { + // for now, until we've solved the mysterious re-rendering + return false + } + + render() { + console.log(this.props.index) + return ( + + {this.makeDayColumn(this.props.item, this.props.index)} + + ) + } +} \ No newline at end of file diff --git a/components/chart/nfp-lines.js b/components/chart/nfp-lines.js new file mode 100644 index 0000000..e091597 --- /dev/null +++ b/components/chart/nfp-lines.js @@ -0,0 +1,78 @@ +import { getCycleStatusForDay } from '../../lib/sympto-adapter' +import { normalizeToScale } from './y-axis' + +export default function () { + let cycleStatus + let cycleStartDate + let noMoreCycles = false + + function updateCurrentCycle(dateString) { + cycleStatus = getCycleStatusForDay(dateString) + if(!cycleStatus) { + noMoreCycles = true + return + } + if (cycleStatus.phases.preOvulatory) { + cycleStartDate = cycleStatus.phases.preOvulatory.start.date + } else { + cycleStartDate = cycleStatus.phases.periOvulatory.start.date + } + } + + function dateIsInPeriOrPostPhase(dateString) { + return ( + dateString >= cycleStatus.phases.periOvulatory.start.date + ) + } + + function precededByAnotherTempValue(dateString) { + return ( + // we are only interested in days that have a preceding + // temp + Object.keys(cycleStatus.phases).some(phaseName => { + return cycleStatus.phases[phaseName].cycleDays.some(day => { + return day.temperature && day.date < dateString + }) + }) + // and also a following temp, so we don't draw the line + // longer than necessary + && + cycleStatus.phases.postOvulatory.cycleDays.some(day => { + return day.temperature && day.date > dateString + }) + ) + } + + function isInTempMeasuringPhase(cycleDay, dateString) { + return ( + cycleDay && cycleDay.temperature + || precededByAnotherTempValue(dateString) + ) + } + + return function(dateString, cycleDay) { + const ret = {} + if (!cycleStatus && !noMoreCycles) updateCurrentCycle(dateString) + if (noMoreCycles) return ret + + if (dateString < cycleStartDate) updateCurrentCycle(dateString) + if (noMoreCycles) return ret + + const tempShift = cycleStatus.temperatureShift + + if (tempShift) { + if (tempShift.firstHighMeasurementDay.date === dateString) { + ret.drawFhmLine = true + } + + if ( + dateIsInPeriOrPostPhase(dateString) && + isInTempMeasuringPhase(cycleDay, dateString) + ) { + ret.drawLtlAt = normalizeToScale(tempShift.ltl) + } + } + + return ret + } +} \ No newline at end of file diff --git a/components/chart/y-axis.js b/components/chart/y-axis.js new file mode 100644 index 0000000..0e9179a --- /dev/null +++ b/components/chart/y-axis.js @@ -0,0 +1,56 @@ +import React from 'react' +import { Text as ReactNativeText } from 'react-native' +import { Line } from 'react-native-svg' +import config from './config' +import styles from './styles' + +function makeYAxis() { + const scaleMin = config.temperatureScale.low + const scaleMax = config.temperatureScale.high + const numberOfTicks = (scaleMax - scaleMin) * 2 + const tickDistance = config.chartHeight / numberOfTicks + + const tickPositions = [] + const labels = [] + // for style reasons, we don't want the first and last tick + for (let i = 1; i < numberOfTicks - 1; i++) { + const y = tickDistance * i + const style = styles.yAxisLabel + // this eyeballing is sadly necessary because RN does not + // support percentage values for transforms, which we'd need + // to reliably place the label vertically centered to the grid + style.top = y - 8 + labels.push( + + {scaleMax - i * 0.5} + + ) + tickPositions.push(y) + } + + return {labels, tickPositions} +} + +export const yAxis = makeYAxis() + +export const horizontalGrid = yAxis.tickPositions.map(tick => { + return ( + + ) +}) + +export function normalizeToScale(temp) { + const scale = config.temperatureScale + const valueRelativeToScale = (scale.high - temp) / (scale.high - scale.low) + const scaleHeight = config.chartHeight + return scaleHeight * valueRelativeToScale +} \ No newline at end of file