diff --git a/components/calendar.js b/components/calendar.js index f91b0396a3c4df0257d09e6a2982b6e6d93ac020..af213ddf4b0933b92e878f964838a236354c8f6b 100644 --- a/components/calendar.js +++ b/components/calendar.js @@ -2,6 +2,7 @@ import React, { Component } from 'react' import { CalendarList } from 'react-native-calendars' import { getOrCreateCycleDay, bleedingDaysSortedByDate } from '../db' import cycleModule from '../lib/cycle' +import {shadesOfRed, shadesOfGrey} from '../styles/index' export default class CalendarView extends Component { constructor(props) { @@ -53,7 +54,6 @@ export default class CalendarView extends Component { } function toCalFormat(bleedingDaysSortedByDate) { - const shadesOfRed = ['#ffcbbf', '#ffb19f', '#ff977e', '#ff7e5f'] // light to dark return bleedingDaysSortedByDate.reduce((acc, day) => { acc[day.date] = { startingDay: true, @@ -66,7 +66,6 @@ function toCalFormat(bleedingDaysSortedByDate) { function predictionToCalFormat(predictedDays) { if (!predictedDays.length) return {} - const shadesOfGrey = ['#e5e5e5', '#cccccc'] // [lighter, darker] const middleIndex = (predictedDays[0].length - 1) / 2 return predictedDays.reduce((acc, setOfDays) => { setOfDays.reduce((accSet, day, i) => { diff --git a/components/chart/chart.js b/components/chart/chart.js index de095407cadf2974cb0e6eb3a0a5d08f3705b283..a4cc7d19014e77a7bc46c98494c4988f68402904 100644 --- a/components/chart/chart.js +++ b/components/chart/chart.js @@ -29,7 +29,6 @@ export default class CycleChart extends Component { onLayout = ({ nativeEvent }) => { if (this.state.chartHeight) return - const height = nativeEvent.layout.height this.setState({ chartHeight: height }) this.reCalculateChartInfo = () => { @@ -62,11 +61,38 @@ export default class CycleChart extends Component { jsDate.getDate() ).toString() }) + const chartSymptoms = [ + 'bleeding', + 'temperature', + 'mucus', + 'cervix', + 'sex', + 'desire', + 'pain', + 'note' + ].filter((symptomName) => { + return cycleDaysSortedByDate.some(cycleDay => cycleDay[symptomName]) + }) const columns = xAxisDates.map(dateString => { const cycleDay = getCycleDay(dateString) - const symptoms = ['temperature', 'mucus', 'bleeding'].reduce((acc, symptom) => { - acc[symptom] = cycleDay && cycleDay[symptom] && cycleDay[symptom].value + const symptoms = chartSymptoms.reduce((acc, symptom) => { + if (symptom === 'bleeding' || + symptom === 'temperature' || + symptom === 'mucus' || + symptom === 'desire' || + symptom === 'note' + ) { + acc[symptom] = cycleDay && cycleDay[symptom] && cycleDay[symptom].value + } else if (symptom === 'cervix') { + acc[symptom] = cycleDay && cycleDay['cervix'] && (cycleDay['cervix'].opening + cycleDay['cervix'].firmness) + } else if (symptom === 'sex') { + // solo = 1 + partner = 2 + acc[symptom] = cycleDay && cycleDay['sex'] && (cycleDay['sex'].solo + cycleDay['sex'].partner) + } else if (symptom === 'pain') { + // is any pain documented? + acc[symptom] = cycleDay && cycleDay['pain'] && Object.values(cycleDay['pain']).some(x => x === true) + } acc[`${symptom}Exclude`] = cycleDay && cycleDay[symptom] && cycleDay[symptom].exclude return acc }, {}) @@ -76,7 +102,7 @@ export default class CycleChart extends Component { return { dateString, y: temp ? normalizeToScale(temp, columnHeight) : null, - ...symptoms, + symptoms, ...getFhmAndLtlInfo(dateString, temp) } }) diff --git a/components/chart/day-column.js b/components/chart/day-column.js index 630abc8406c9538312d07d50e5b6b0bd5e4305ff..9b010fda01c53c7a5040e72addfceb590d7b354d 100644 --- a/components/chart/day-column.js +++ b/components/chart/day-column.js @@ -27,8 +27,7 @@ export default class DayColumn extends Component { dateString, y, temperatureExclude, - bleeding, - mucus, + symptoms, drawFhmLine, drawLtlAt, rightY, @@ -72,8 +71,8 @@ export default class DayColumn extends Component { const cycleDayNumber = getCycleDayNumber(dateString) const shortDate = dateString.split('-').slice(1).join('-') const cycleDayLabel = ( - <Text style={label.number}> - {cycleDayNumber} + <Text style = {label.number}> + {cycleDayNumber ? cycleDayNumber : ' '} </Text>) const dateLabel = ( <Text style = {label.date}> @@ -83,20 +82,19 @@ export default class DayColumn extends Component { // we merge the colors here instead of from the stylesheet because of a RN // bug that doesn't apply borderLeftColor otherwise - const customStyle = { + const potentialCustomStyle = { height: columnHeight, borderLeftColor: 'grey', - borderRightColor: 'grey' } if (drawFhmLine) { - customStyle.borderLeftColor = styles.nfpLine.borderColor - customStyle.borderLeftWidth = 3 + potentialCustomStyle.borderLeftColor = styles.nfpLine.borderColor + potentialCustomStyle.borderLeftWidth = 3 } const column = React.createElement( TouchableOpacity, { - style: [styles.column.rect, customStyle], + style: [styles.column.rect, potentialCustomStyle], key: this.props.index.toString(), onPress: () => { this.passDateToDayView(dateString) @@ -108,24 +106,73 @@ export default class DayColumn extends Component { return ( <View> - <View style={[styles.symptomRow, {height: symptomHeight}]}> - {typeof mucus === 'number' && - <View - {...styles.mucusIcon} - backgroundColor={styles.mucusIconShades[mucus]} - key='mucus' - /> - } - {typeof bleeding === 'number' && - <Icon - name='drop' - size={18} - color='#900' - key='bleeding' - /> - } + <View height={symptomHeight}> + <View style={styles.symptomRow}> + {typeof symptoms.bleeding === 'number' && + <Icon + name='drop' + size={12} + color={styles.bleedingIconShades[symptoms.bleeding]} + key='bleeding' + /> + } + </View> + <View style={styles.symptomRow}> + {typeof symptoms.mucus === 'number' && + <View + {...styles.mucusIcon} + backgroundColor={styles.mucusIconShades[symptoms.mucus]} + key='mucus' + /> + } + </View> + <View style={styles.symptomRow}> + {typeof symptoms.cervix === 'number' && + <View + {...styles.mucusIcon} + // cervix is sum of openess and firmness - fertile only when closed and hard (=0) + backgroundColor={symptoms.cervix > 0 ? 'blue' : 'green'} + key='cervix' + /> + } + </View> + <View style={styles.symptomRow}> + {typeof symptoms.sex === 'number' && + <View + {...styles.mucusIcon} + backgroundColor='orange' + key='sex' + /> + } + </View> + <View style={styles.symptomRow}> + {typeof symptoms.desire === 'number' && + <View + {...styles.mucusIcon} + backgroundColor='red' + key='desire' + /> + } + </View> + <View style={styles.symptomRow}> + {symptoms.pain && + <View + {...styles.mucusIcon} + backgroundColor='blue' + key='pain' + /> + } + </View> + <View style={styles.symptomRow}> + {symptoms.note && + <View + {...styles.mucusIcon} + backgroundColor='green' + key='note' + /> + } + </View> </View> - {column} <View style={{height: xAxisHeight}}> diff --git a/components/chart/dot-and-line.js b/components/chart/dot-and-line.js index 418457f005888e5fd6ec327dcecd2162a5f57021..9d6daed3004eea796a9c57dfa0ca7489fb34531e 100644 --- a/components/chart/dot-and-line.js +++ b/components/chart/dot-and-line.js @@ -41,7 +41,7 @@ export default class DotAndLine extends Component { function makeLine(leftY, rightY, direction, excludeLine) { const colWidth = config.columnWidth - const heightDiff = -leftY - -rightY + const heightDiff = -(leftY - rightY) const angle = Math.atan2(heightDiff, colWidth / 2) const lineStyle = excludeLine ? styles.curveExcluded : styles.curve // hypotenuse, we add 3px for good measure, because otherwise the lines diff --git a/components/chart/styles.js b/components/chart/styles.js index d7ab484d87067882f375750f7d1c4889ec543f90..56cede6454ad4da3966ba475d27f7c72315983d1 100644 --- a/components/chart/styles.js +++ b/components/chart/styles.js @@ -1,26 +1,33 @@ import config from '../../config' +import {primaryColor, shadesOfRed} from '../../styles/index' + +const colorTemperature = '#765285' +const colorTemperatureLight = '#a67fb5' +const dotWidth = 10 +const lineWidth = 2 +const colorLtl = '#feb47b' const styles = { curve: { borderStyle: 'solid', - borderColor: '#ffc425', - borderWidth: 2, + borderColor: colorTemperature, + borderWidth: lineWidth, }, curveExcluded: { - borderColor: 'lightgrey', - borderWidth: 2, - borderStyle: 'solid' + borderColor: colorTemperatureLight, + borderWidth: lineWidth, + borderStyle: 'dotted' }, curveDots: { - backgroundColor: '#00aedb', - width: 12, - height: 12, + backgroundColor: colorTemperature, + width: dotWidth, + height: dotWidth, borderRadius: 50 }, curveDotsExcluded: { - backgroundColor: 'lightgrey', - width: 12, - height: 12, + backgroundColor: colorTemperatureLight, + width: dotWidth, + height: dotWidth, borderRadius: 50 }, column: { @@ -28,19 +35,18 @@ const styles = { date: { color: 'grey', fontSize: 9, - fontWeight: '100' + fontWeight: '100', }, number: { - color: '#00b159', + color: primaryColor, fontSize: 13, - textAlign: 'center' + textAlign: 'center', } }, rect: { width: config.columnWidth, borderStyle: 'solid', borderLeftWidth: 0.5, - borderRightWidth: 0.5, } }, bleedingIcon: { @@ -49,20 +55,21 @@ const styles = { x: 6, y: 3 }, + bleedingIconShades: shadesOfRed, mucusIcon: { width: 12, height: 12, borderRadius: 50, }, mucusIconShades: [ - '#cc99cc', - '#bf7fbf', - '#b266b2', - '#a64ca6', - '#993299' + '#fef0e4', + '#fee1ca', + '#fed2af', + '#fec395', + '#feb47b' ], yAxis: { - width: config.columnWidth, + width: 27, borderRightWidth: 0.5, borderColor: 'lightgrey', borderStyle: 'solid' @@ -83,13 +90,15 @@ const styles = { left: config.columnWidth }, nfpLine: { - borderColor: '#00b159', - borderWidth: 2, + borderColor: colorLtl, + borderWidth: lineWidth, borderStyle: 'solid' }, symptomRow: { alignItems: 'center', - justifyContent: 'center' + justifyContent: 'center', + width: '100%', + flex: 1 } } diff --git a/components/chart/y-axis.js b/components/chart/y-axis.js index 1bd3312ff25fc962746d3bb043e6784ccd162246..12fa08c9a230ac7fe196fac400d41ae78fc7ce5c 100644 --- a/components/chart/y-axis.js +++ b/components/chart/y-axis.js @@ -2,24 +2,34 @@ import React from 'react' import { View } from 'react-native' import config from '../../config' import styles from './styles' -import { scaleObservable } from '../../local-storage' +import { scaleObservable, unitObservable } from '../../local-storage' import { AppText } from '../app-text' export function makeYAxisLabels(columnHeight) { - const units = config.temperatureScale.units + const units = unitObservable.value const scaleMax = scaleObservable.value.max const style = styles.yAxisLabel return getTickPositions(columnHeight).map((y, i) => { + const tick = scaleMax - i * units + const tickLabel = tick * 10 % 10 ? tick.toString() : tick.toString() + '.0' + let showTick + let tickBold + if (units === 0.1) { + showTick = (tick * 10 % 2) ? false : true + tickBold = tick * 10 % 5 ? {} : {fontWeight: 'bold'} + } else { + showTick = (tick * 10 % 5) ? false : true + tickBold = tick * 10 % 10 ? {} : {fontWeight: 'bold'} + } // 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 - if (scaleMax - i * units === 37) console.log(y) return ( <AppText - style={[style, {top: y - 8}]} + style={[style, {top: y - 8}, tickBold]} key={i}> - {scaleMax - i * units} + {showTick && tickLabel} </AppText> ) }) @@ -38,12 +48,11 @@ export function makeHorizontalGrid(columnHeight, symptomRowHeight) { } function getTickPositions(columnHeight) { - const units = config.temperatureScale.units + const units = unitObservable.value const scaleMin = scaleObservable.value.min const scaleMax = scaleObservable.value.max const numberOfTicks = (scaleMax - scaleMin) * (1 / units) + 1 const tickDistance = 1 / (numberOfTicks - 1) - const tickPositions = [] for (let i = 0; i < numberOfTicks; i++) { const position = getAbsoluteValue(tickDistance * i, columnHeight) @@ -63,5 +72,4 @@ function getAbsoluteValue(relative, columnHeight) { const verticalPadding = columnHeight * config.temperatureScale.verticalPadding const scaleHeight = columnHeight - 2 * verticalPadding return scaleHeight * relative + verticalPadding - } \ No newline at end of file diff --git a/config.js b/config.js index 772e8282121b76b3e8f1dff970b68f25c64d3a8d..7cea4e1f1f616630c88582682c8c5dd82cd11ae8 100644 --- a/config.js +++ b/config.js @@ -1,8 +1,8 @@ const config = { columnWidth: 25, - columnHeightPercentage: 0.84, + columnHeightPercentage: 0.62, xAxisHeightPercentage: 0.08, - symptomRowHeightPercentage: 0.08, + symptomRowHeightPercentage: 0.30, temperatureScale: { defaultLow: 35, defaultHigh: 38, diff --git a/local-storage/index.js b/local-storage/index.js index 6296271101fdca17ba796fc7a0aef570f1e86dcf..83f50e52cd5eb011196677b392055cd9cb377d44 100644 --- a/local-storage/index.js +++ b/local-storage/index.js @@ -8,6 +8,18 @@ setObvWithInitValue('tempScale', scaleObservable, { max: config.temperatureScale.defaultHigh }) +export const unitObservable = Observable() +unitObservable.set(config.temperatureScale.units) +scaleObservable((scale) => { + const scaleRange = scale.max - scale.min + if (scaleRange <= 3) { + unitObservable.set(0.1) + } else { + unitObservable.set(0.5) + } +}) + + export const tempReminderObservable = Observable() setObvWithInitValue('tempReminder', tempReminderObservable, { enabled: false diff --git a/styles/index.js b/styles/index.js index e90cac237fab98fb8483b1fe18d6119c3b731060..c0fec2438110a27b50c770025699d34ed18ca1aa 100644 --- a/styles/index.js +++ b/styles/index.js @@ -3,6 +3,8 @@ import { StyleSheet } from 'react-native' export const primaryColor = '#ff7e5f' export const secondaryColor = '#351c4d' export const fontOnPrimaryColor = 'white' +export const shadesOfRed = ['#ffcbbf', '#ffb19f', '#ff977e', '#ff7e5f'] // light to dark +export const shadesOfGrey = ['#e5e5e5', '#cccccc'] // [lighter, darker] const defaultBottomMargin = 5 const defaultIndentation = 10