diff --git a/components/chart/chart.js b/components/chart/chart.js index 449a120..9393407 100644 --- a/components/chart/chart.js +++ b/components/chart/chart.js @@ -1,17 +1,29 @@ import React, { Component } from 'react' -import { Text, View, FlatList, ScrollView } from 'react-native' +import { View, FlatList } from 'react-native' import range from 'date-range' import { LocalDate } from 'js-joda' -import Icon from 'react-native-vector-icons/Entypo' -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 yAxisView = {yAxis.labels} -const yAxis = makeYAxis(config) +function getInfoForNeighborColumns(index, cols) { + const ret = {} + 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 +} export default class CycleChart extends Component { constructor(props) { @@ -19,6 +31,16 @@ export default class CycleChart extends Component { this.state = { columns: makeColumnInfo(config.xAxisRangeInDays) } + this.renderColumn = ({item, index}) => { + return ( + + ) + } this.reCalculateChartInfo = (function(Chart) { return function() { @@ -33,186 +55,22 @@ export default class CycleChart extends Component { cycleDaysSortedByDate.removeListener(this.reCalculateChartInfo) } - passDateToDayView(dateString) { - const cycleDay = getOrCreateCycleDay(dateString) - this.props.navigation.navigate('cycleDay', { cycleDay }) - } - - placeHorizontalGrid() { - } - - makeDayColumn({ dateString, cycleDay, y }, index) { - const cycleDayNumber = getCycleDayNumber(dateString) - const label = styles.column.label - const dateText = dateString.split('-').slice(1).join('-') - const getFhmAndLtlInfo = setUpFertilityStatusFunc() - const nfpLineInfo = getFhmAndLtlInfo(dateString, cycleDay) - - const horizontalGrid = yAxis.labels.map((_, i) => { - return React.createElement( - View, - { - style: Object.assign( - {}, - styles.horizontalGrid, - { marginTop: yAxis.tickDistance } - ), - key: i.toString() - } - ) - }) - //TODO move these so they are visible - const cycleDayLabel = ( - - {cycleDayNumber} - ) - const dateLabel = ( - - {dateText} - - ) - const columnElements = [] - if (cycleDay && cycleDay.bleeding) { - console.log('ever?') - columnElements.push( - - ) - } - columnElements.push(...[horizontalGrid, cycleDayLabel, dateLabel]) - // {nfpLineInfo.drawFhmLine ? - // : null} - // />) - - // onPress: () => this.passDateToDayView(dateString), - - // - // : null} - - // {nfpLineInfo.drawLtlAt ? - // : null} - - if (y) { - columnElements.push(this.drawDotAndLines(y, cycleDay.temperature.exclude, index)) - } - // {cycleDay && cycleDay.mucus ? - // : null} - - // {y ? - // this.drawDotAndLines(y, cycleDay.temperature.exclude, index) - // : null} */} - - return React.createElement( - View, - { - style: styles.column.rect, - key: index.toString() - }, - columnElements - ) - } - - drawDotAndLines(currY, exclude, index) { - /* - ) */ - let lineToRight - let lineToLeft - const cols = this.state.columns - - 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 = index > 0 && cols[index - 1].y - const thereIsADotToTheLeft = index < cols.length - 1 && cols[index + 1].y - - /* if (thereIsADotToTheRight) { - const otherDot = cols[index - 1] - const excludedLine = otherDot.cycleDay.temperature.exclude || exclude - lineToRight = makeLine(otherDot.y, config.columnWidth, excludedLine) - } - if (thereIsADotToTheLeft) { - const otherDot = cols[index + 1] - const excludedLine = otherDot.cycleDay.temperature.exclude || exclude - lineToLeft = makeLine(otherDot.y, 0, excludedLine) - } */ - - const dotStyle = exclude ? styles.curveDotsExcluded : styles.curveDots - return [ - /* {lineToRight} - {lineToLeft} */ - - ] - } - render() { return ( - - {yAxis.labels} + + {yAxisView} { - return this.makeDayColumn(item, index) - }} + renderItem={this.renderColumn} keyExtractor={item => item.dateString} - initialNumToRender={20} - /> - + initialNumToRender={15} + maxToRenderPerBatch={5} + > + + ) } } @@ -228,11 +86,16 @@ function makeColumnInfo(n) { return xAxisDates.map(dateString => { const cycleDay = getCycleDay(dateString) - const temp = cycleDay && cycleDay.temperature && cycleDay.temperature.value + const symptoms = ['temperature', 'mucus', 'bleeding'].reduce((acc, symptom) => { + acc[symptom] = cycleDay && cycleDay[symptom] && cycleDay[symptom].value + acc[`${symptom}Exclude`] = cycleDay && cycleDay[symptom] && cycleDay[symptom].exclude + return acc + }, {}) + return { dateString, - cycleDay, - y: temp ? normalizeToScale(temp) : null + y: symptoms.temperature ? normalizeToScale(symptoms.temperature) : null, + ...symptoms } }) } @@ -246,114 +109,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 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} - - ) - } - - return {labels, tickDistance} -} - -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/config.js b/components/chart/config.js index d36413a..e9d5f95 100644 --- a/components/chart/config.js +++ b/components/chart/config.js @@ -2,10 +2,10 @@ const config = { chartHeight: 350, columnWidth: 30, temperatureScale: { - low: 33, + low: 35, high: 40 }, - xAxisRangeInDays: 30 + xAxisRangeInDays: 50 } const margin = 3 diff --git a/components/chart/day-column.js b/components/chart/day-column.js new file mode 100644 index 0000000..cdcb015 --- /dev/null +++ b/components/chart/day-column.js @@ -0,0 +1,175 @@ +import React, { Component } from 'react' +import { + Text, View +} from 'react-native' +import Icon from 'react-native-vector-icons/Entypo' +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' +import slowlog from 'react-native-slowlog' + +const getCycleDayNumber = cycleModule().getCycleDayNumber +const label = styles.column.label +const getFhmAndLtlInfo = setUpFertilityStatusFunc() + +export default class DayColumn extends Component { + constructor(props) { + super(props) + slowlog(this, /.*/, {threshold: 30}) + } + makeDayColumn(data, index) { + const { + dateString, + y, + temperature, + temperatureExclude, + bleeding, + mucus + } = data + const cycleDayNumber = getCycleDayNumber(dateString) + const shortDate = dateString.split('-').slice(1).join('-') + const nfpLineInfo = getFhmAndLtlInfo(dateString, temperature) + + //TODO move these so they are visible + const cycleDayLabel = ( + + {cycleDayNumber} + ) + const dateLabel = ( + + {shortDate} + + ) + const columnElements = [] + if (bleeding) { + columnElements.push( + + ) + } + columnElements.push(...[horizontalGrid, cycleDayLabel, dateLabel]) + // {nfpLineInfo.drawFhmLine ? + // : null} + // />) + + // onPress: () => this.passDateToDayView(dateString), + + // + // : null} + + // {nfpLineInfo.drawLtlAt ? + // : null} + + if (y) { + columnElements.push(this.drawDotAndLines(y, temperatureExclude, index)) + } + // {cycleDay && cycleDay.mucus ? + // : null} + + // {y ? + // this.drawDotAndLines(y, cycleDay.temperature.exclude, index) + // : null} */} + + return React.createElement( + View, + { + style: styles.column.rect, + key: index.toString() + }, + columnElements + ) + } + + 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 + } */ + + /* if (this.props.rightY) { + const excludedLine = this.props.rightTemperatureExclude || exclude + lineToRight = makeLine(this.props.rightY, config.columnWidth, excludedLine) + } + if (this.props.leftY) { + const excludedLine = this.props.leftTemperatureExclude || exclude + lineToLeft = makeLine(this.props.leftY, 0, excludedLine) + } */ + + const dotStyle = exclude ? styles.curveDotsExcluded : styles.curveDots +/* return ( + {lineToRight} + {lineToLeft} + + ) */ + } + + passDateToDayView(dateString) { + const cycleDay = getOrCreateCycleDay(dateString) + this.props.navigate('cycleDay', { cycleDay }) + } + + shouldComponentUpdate(newProps) { + return Object.keys(newProps).some(key => newProps[key] != this.props[key]) + } + + render() { + 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..e5265ac --- /dev/null +++ b/components/chart/nfp-lines.js @@ -0,0 +1,77 @@ +import { getCycleStatusForDay } from '../../lib/sympto-adapter' +import { normalizeToScale } from './y-axis' + +export default function () { + const cycle = { + status: null + } + + function updateCurrentCycle(dateString) { + cycle.status = getCycleStatusForDay(dateString) + if(!cycle.status) { + cycle.noMoreCycles = true + return + } + if (cycle.status.phases.preOvulatory) { + cycle.startDate = cycle.status.phases.preOvulatory.start.date + } else { + cycle.startDate = cycle.status.phases.periOvulatory.start.date + } + } + + function dateIsInPeriOrPostPhase(dateString) { + return ( + dateString >= cycle.status.phases.periOvulatory.start.date + ) + } + + function precededByAnotherTempValue(dateString) { + return ( + // we are only interested in days that have a preceding + // temp + Object.keys(cycle.status.phases).some(phaseName => { + return cycle.status.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 + && + cycle.status.phases.postOvulatory.cycleDays.some(day => { + return day.temperature && day.date > dateString + }) + ) + } + + function isInTempMeasuringPhase(temperature, dateString) { + return ( + temperature || precededByAnotherTempValue(dateString) + ) + } + + return function(dateString, temperature) { + const ret = {} + if (!cycle.status && !cycle.noMoreCycles) updateCurrentCycle(dateString) + if (cycle.noMoreCycles) return ret + + if (dateString < cycle.startDate) updateCurrentCycle(dateString) + if (cycle.noMoreCycles) return ret + + const tempShift = cycle.status.temperatureShift + + if (tempShift) { + if (tempShift.firstHighMeasurementDay.date === dateString) { + ret.drawFhmLine = true + } + + if ( + dateIsInPeriOrPostPhase(dateString) && + isInTempMeasuringPhase(temperature, 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..e90dcac --- /dev/null +++ b/components/chart/y-axis.js @@ -0,0 +1,54 @@ +import React from 'react' +import { Text, View } from 'react-native' +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 diff --git a/package-lock.json b/package-lock.json index 86dcf00..b0cdbd3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6426,6 +6426,11 @@ "resolved": "https://registry.npmjs.org/react-native-simple-radio-button/-/react-native-simple-radio-button-2.7.2.tgz", "integrity": "sha512-BdlllHsC/gYJtxPJ2tshDWN8CzmlGg1G9uB+Lu4FRGvGkwhvMtJ/uNShMbvxu134xosH/feri6HQgLGlIT202Q==" }, + "react-native-slowlog": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/react-native-slowlog/-/react-native-slowlog-1.0.2.tgz", + "integrity": "sha1-VSCXnj751Sc0ldQx/zvjTwLjXIk=" + }, "react-native-tab-view": { "version": "0.0.77", "resolved": "https://registry.npmjs.org/react-native-tab-view/-/react-native-tab-view-0.0.77.tgz", diff --git a/package.json b/package.json index 58f2638..cf36e50 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "react-native-share": "^1.1.0", "react-native-simple-radio-button": "^2.7.1", "react-native-vector-icons": "^5.0.0", + "react-native-slowlog": "^1.0.2", "react-navigation": "^2.0.4", "realm": "^2.7.1", "uuid": "^3.2.1"