diff --git a/components/chart/chart-legend.js b/components/chart/chart-legend.js
new file mode 100644
index 0000000..df6f8a6
--- /dev/null
+++ b/components/chart/chart-legend.js
@@ -0,0 +1,27 @@
+import React from 'react'
+import { View } from 'react-native'
+
+import AppText from '../app-text'
+import DripHomeIcon from '../../assets/drip-home-icons'
+
+import styles from './styles'
+import { cycleDayColor } from '../../styles'
+
+import { shared as labels } from '../../i18n/en/labels'
+
+const ChartLegend = () => {
+ return (
+
+
+
+ {labels.date.toLowerCase()}
+
+
+ )
+}
+
+export default ChartLegend
diff --git a/components/chart/chart.js b/components/chart/chart.js
index 4106938..8f8a3f5 100644
--- a/components/chart/chart.js
+++ b/components/chart/chart.js
@@ -1,31 +1,20 @@
import React, { Component } from 'react'
import { View, FlatList, ActivityIndicator } from 'react-native'
import { LocalDate } from 'js-joda'
-import { makeYAxisLabels, makeHorizontalGrid } from './y-axis'
+
+import YAxis from './y-axis'
import nfpLines from './nfp-lines'
import DayColumn from './day-column'
+import HorizontalGrid from './horizontal-grid'
+
import { getCycleDaysSortedByDate, getAmountOfCycleDays } from '../../db'
import styles from './styles'
-import { cycleDayColor } from '../../styles'
import { scaleObservable } from '../../local-storage'
import config from '../../config'
-import AppText from '../app-text'
-import AppLoadingView from '../app-loading'
-import { shared as labels } from '../../i18n/en/labels'
-import DripIcon from '../../assets/drip-icons'
-import DripHomeIcon from '../../assets/drip-home-icons'
-import nothingChanged from '../../db/db-unchanged'
-const symptomIcons = {
- bleeding: ,
- mucus: ,
- cervix: ,
- desire: ,
- sex: ,
- pain: ,
- mood: ,
- note:
-}
+import AppLoadingView from '../app-loading'
+
+import nothingChanged from '../../db/db-unchanged'
export default class CycleChart extends Component {
constructor(props) {
@@ -129,49 +118,30 @@ export default class CycleChart extends Component {
}
render() {
+ const { chartHeight, chartLoaded } = this.state
return (
- {!this.state.chartLoaded && }
+ {!chartLoaded && }
- {this.state.chartHeight && this.state.chartLoaded &&
-
-
- {this.symptomRowSymptoms.map(symptomName => {
- return
- {symptomIcons[symptomName]}
-
- })}
-
-
- {makeYAxisLabels(this.columnHeight)}
-
-
-
-
- {labels.date.toLowerCase()}
-
-
- }
+ {chartHeight && chartLoaded && (
+
+ )}
-
- {this.state.chartHeight && this.state.chartLoaded &&
- makeHorizontalGrid(this.columnHeight, this.symptomRowHeight)
+ {chartHeight && chartLoaded && (
+ )
}
- {this.state.chartHeight &&
+ {chartHeight &&
{
-
- const { symptomHeight } = this.props
- const shouldDrawSymptom = this.data.hasOwnProperty(symptom)
- const styleParent = [styles.symptomRow, {height: symptomHeight}]
-
- if (shouldDrawSymptom) {
- const styleSymptom = styles.iconShades[symptom]
- const symptomData = this.data[symptom]
-
- const dataIsComplete = this.isSymptomDataComplete(symptom)
- const isMucusOrCervix = (symptom === 'mucus') || (symptom === 'cervix')
-
- const backgroundColor = (isMucusOrCervix && !dataIsComplete) ?
- 'white' : styleSymptom[symptomData]
- const borderWidth = (isMucusOrCervix && !dataIsComplete) ? 2 : 0
- const borderColor = styleSymptom[0]
- const styleChild = [styles.symptomIcon, {
- backgroundColor,
- borderColor,
- borderWidth
- }]
-
- return (
-
-
-
- )
- } else {
- return (
-
- )
- }
- }
-
render() {
const columnElements = []
const { dateString,
@@ -263,9 +231,21 @@ class DayColumn extends Component {
onPress={() => this.onDaySelect(dateString)}
activeOpacity={1}
>
-
- {symptomRowSymptoms.map(symptom => this.drawSymptom(symptom))}
-
+
+ { symptomRowSymptoms.map(symptom => {
+ const hasSymptomData = this.data.hasOwnProperty(symptom)
+ return (
+ )
+ }
+ )}
{column}
diff --git a/components/chart/horizontal-grid.js b/components/chart/horizontal-grid.js
new file mode 100644
index 0000000..e7cf7cc
--- /dev/null
+++ b/components/chart/horizontal-grid.js
@@ -0,0 +1,26 @@
+import React from 'react'
+import PropTypes from 'prop-types'
+import { View } from 'react-native'
+
+import { getTickPositions } from '../helpers/chart'
+
+import styles from './styles'
+
+const HorizontalGrid = ({ height, startPosition }) => {
+ return getTickPositions(height).map(tick => {
+ return (
+
+ )
+ })
+}
+
+HorizontalGrid.propTypes = {
+ height: PropTypes.number,
+ startPosition: PropTypes.number,
+}
+
+export default HorizontalGrid
diff --git a/components/chart/nfp-lines.js b/components/chart/nfp-lines.js
index cf73c52..853dde5 100644
--- a/components/chart/nfp-lines.js
+++ b/components/chart/nfp-lines.js
@@ -1,5 +1,5 @@
import { getCycleStatusForDay } from '../../lib/sympto-adapter'
-import { normalizeToScale } from './y-axis'
+import { normalizeToScale } from '../helpers/chart'
export default function () {
const cycle = {
diff --git a/components/chart/styles.js b/components/chart/styles.js
index d2a95d7..dc3a69b 100644
--- a/components/chart/styles.js
+++ b/components/chart/styles.js
@@ -11,6 +11,19 @@ const gridLineWidthVertical = 0.6
const gridLineWidthHorizontal = 0.3
const numberLabelFontSize = 13
+const redColor = '#c3000d'
+const violetColor = '#7689a9'
+const shadesOfViolet = ['#e3e7ed', '#c8cfdc', '#acb8cb', '#91a0ba', violetColor] // light to dark
+const yellowColor = '#dbb40c'
+const shadesOfYellow = ['#f0e19d', '#e9d26d', '#e2c33c', yellowColor] // light to dark
+const magentaColor = '#6f2565'
+const shadesOfMagenta = ['#a87ca2', '#8b5083', magentaColor] // light to dark
+const pinkColor = '#9e346c'
+const shadesOfPink = ['#c485a6', '#b15c89', pinkColor] // light to dark
+const lightGreenColor = '#bccd67'
+const orangeColor = '#bc6642'
+const mintColor = '#6ca299'
+
const styles = {
curve: {
stroke: colorTemperature,
@@ -48,39 +61,44 @@ const styles = {
width: gridLineWidthVertical,
}
},
- symptomIcon: {
+ symptomDot: {
width: 12,
height: 12,
borderRadius: 50,
},
- iconShades: {
- 'bleeding': shadesOfRed,
- 'mucus': [
- '#e3e7ed',
- '#c8cfdc',
- '#acb8cb',
- '#91a0ba',
- '#7689a9'
- ],
- 'cervix': [
- '#f0e19d',
- '#e9d26d',
- '#e2c33c',
- '#dbb40c',
- ],
- 'sex': [
- '#a87ca2',
- '#8b5083',
- '#6f2565',
- ],
- 'desire': [
- '#c485a6',
- '#b15c89',
- '#9e346c',
- ],
- 'pain': ['#bccd67'],
- 'mood': ['#bc6642'],
- 'note': ['#6ca299']
+ iconColors: {
+ 'bleeding': {
+ color: redColor,
+ shades: shadesOfRed,
+ },
+ 'mucus': {
+ color: violetColor,
+ shades: shadesOfViolet,
+ },
+ 'cervix': {
+ color: yellowColor,
+ shades: shadesOfYellow,
+ },
+ 'sex': {
+ color: magentaColor,
+ shades: shadesOfMagenta,
+ },
+ 'desire': {
+ color: pinkColor,
+ shades: shadesOfPink,
+ },
+ 'pain': {
+ color: lightGreenColor,
+ shades: [lightGreenColor],
+ },
+ 'mood': {
+ color: orangeColor,
+ shades: [orangeColor],
+ },
+ 'note': {
+ color: mintColor,
+ shades: [mintColor],
+ },
},
yAxis: {
width: 27,
@@ -109,6 +127,18 @@ const styles = {
fontWeight: '100',
}
},
+ symptomIcon: {
+ alignItems: 'center',
+ justifyContent: 'center',
+ },
+ chartLegend: {
+ alignItems: 'center',
+ justifyContent: 'center',
+ },
+ boldTick: {
+ fontWeight: 'bold',
+ fontSize: 11,
+ },
horizontalGrid: {
position:'absolute',
borderStyle: 'solid',
diff --git a/components/chart/symptom-cell.js b/components/chart/symptom-cell.js
new file mode 100644
index 0000000..fa76042
--- /dev/null
+++ b/components/chart/symptom-cell.js
@@ -0,0 +1,52 @@
+import React from 'react'
+import PropTypes from 'prop-types'
+import { View } from 'react-native'
+
+import styles from './styles'
+
+const SymptomCell = ({
+ height,
+ symptom,
+ symptomValue,
+ isSymptomDataComplete
+}) => {
+
+ const shouldDrawDot = symptomValue !== false
+ const styleParent = [styles.symptomRow, { height }]
+ let styleChild
+
+ if (shouldDrawDot) {
+ const styleSymptom = styles.iconColors[symptom]
+ const symptomColor = styleSymptom.shades[symptomValue]
+
+ const isMucusOrCervix = (symptom === 'mucus') || (symptom === 'cervix')
+
+ const backgroundColor = (isMucusOrCervix && !isSymptomDataComplete) ?
+ 'white' : symptomColor
+ const borderWidth = (isMucusOrCervix && !isSymptomDataComplete) ? 2 : 0
+ const borderColor = symptomColor
+ styleChild = [styles.symptomDot, {
+ backgroundColor,
+ borderColor,
+ borderWidth
+ }]
+ }
+
+ return (
+
+ {shouldDrawDot && }
+
+ )
+}
+
+SymptomCell.propTypes = {
+ height: PropTypes.number,
+ symptom: PropTypes.string,
+ symptomValue: PropTypes.oneOfType([
+ PropTypes.bool,
+ PropTypes.number,
+ ]),
+ isSymptomDataComplete: PropTypes.bool,
+}
+
+export default SymptomCell
diff --git a/components/chart/symptom-icon.js b/components/chart/symptom-icon.js
new file mode 100644
index 0000000..3236c3b
--- /dev/null
+++ b/components/chart/symptom-icon.js
@@ -0,0 +1,26 @@
+import React from 'react'
+import PropTypes from 'prop-types'
+import { View } from 'react-native'
+
+import DripIcon from '../../assets/drip-icons'
+
+import styles from './styles'
+
+const SymptomIcon = ({ symptom, height }) => {
+ return (
+
+
+
+ )
+}
+
+SymptomIcon.propTypes = {
+ height: PropTypes.number,
+ symptom: PropTypes.string,
+}
+
+export default SymptomIcon
diff --git a/components/chart/tick-list.js b/components/chart/tick-list.js
new file mode 100644
index 0000000..16fba0a
--- /dev/null
+++ b/components/chart/tick-list.js
@@ -0,0 +1,34 @@
+import React from 'react'
+import PropTypes from 'prop-types'
+import { View } from 'react-native'
+
+import Tick from './tick'
+
+import { getTickList } from '../helpers/chart'
+
+import styles from './styles'
+
+const TickList = ({ height }) => {
+ return (
+ {
+ getTickList(height)
+ .map(({ label, position, isBold, shouldShowLabel}) => {
+ return (
+
+ )
+ })
+ }
+ )
+}
+
+TickList.propTypes = {
+ height: PropTypes.number,
+}
+
+export default TickList
diff --git a/components/chart/tick.js b/components/chart/tick.js
new file mode 100644
index 0000000..173cfbe
--- /dev/null
+++ b/components/chart/tick.js
@@ -0,0 +1,29 @@
+import React from 'react'
+import PropTypes from 'prop-types'
+
+import AppText from '../app-text'
+
+import styles from './styles'
+
+const Tick = ({ yPosition, isBold, shouldShowLabel, label }) => {
+ // 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
+ const topPosition = yPosition - 8
+ const style = [
+ styles.yAxisLabels.tempScale,
+ {top: topPosition},
+ isBold && styles.boldTick
+ ]
+
+ return {shouldShowLabel && label}
+}
+
+Tick.propTypes = {
+ yPosition: PropTypes.number,
+ isBold: PropTypes.bool,
+ shouldShowLabel: PropTypes.bool,
+ label: PropTypes.string,
+}
+
+export default Tick
diff --git a/components/chart/y-axis.js b/components/chart/y-axis.js
index 1f33736..acd3ccf 100644
--- a/components/chart/y-axis.js
+++ b/components/chart/y-axis.js
@@ -1,75 +1,37 @@
import React from 'react'
+import PropTypes from 'prop-types'
import { View } from 'react-native'
-import config from '../../config'
+
+import SymptomIcon from './symptom-icon'
+import TickList from './tick-list'
+import ChartLegend from './chart-legend'
+
import styles from './styles'
-import { scaleObservable, unitObservable } from '../../local-storage'
-import AppText from '../app-text'
-export function makeYAxisLabels(columnHeight) {
- const units = unitObservable.value
- const scaleMax = scaleObservable.value.max
- const style = styles.yAxisLabels.tempScale
-
- 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', fontSize: 11}
- } else {
- showTick = (tick * 10 % 5) ? false : true
- tickBold = tick * 10 % 10 ? {} : {fontWeight: 'bold', fontSize: 11}
- }
- // 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
- return (
-
- {showTick && tickLabel}
-
- )
- })
+const YAxis = ({ height, symptomsToDisplay, symptomsSectionHeight }) => {
+ const symptomIconHeight = symptomsSectionHeight / symptomsToDisplay.length
+ return (
+
+
+ {symptomsToDisplay.map(symptom => (
+
+ )
+ )}
+
+
+
+
+ )
}
-export function makeHorizontalGrid(columnHeight, symptomRowHeight) {
- return getTickPositions(columnHeight).map(tick => {
- return (
-
- )
- })
+YAxis.propTypes = {
+ height: PropTypes.number,
+ symptomsToDisplay: PropTypes.array,
+ symptomsSectionHeight: PropTypes.number,
}
-function getTickPositions(columnHeight) {
- 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)
- tickPositions.push(position)
- }
- return tickPositions
-}
-
-export function normalizeToScale(temp, columnHeight) {
- const scale = scaleObservable.value
- const valueRelativeToScale = (scale.max - temp) / (scale.max - scale.min)
- return getAbsoluteValue(valueRelativeToScale, columnHeight)
-}
-
-function getAbsoluteValue(relative, columnHeight) {
- // we add some height to have some breathing room
- const verticalPadding = columnHeight * config.temperatureScale.verticalPadding
- const scaleHeight = columnHeight - 2 * verticalPadding
- return scaleHeight * relative + verticalPadding
-}
\ No newline at end of file
+export default YAxis
diff --git a/components/helpers/chart.js b/components/helpers/chart.js
new file mode 100644
index 0000000..24a563c
--- /dev/null
+++ b/components/helpers/chart.js
@@ -0,0 +1,67 @@
+import { scaleObservable, unitObservable } from '../../local-storage'
+import config from '../../config'
+
+export function normalizeToScale(temp, columnHeight) {
+ const scale = scaleObservable.value
+ const valueRelativeToScale = (scale.max - temp) / (scale.max - scale.min)
+ return getAbsoluteValue(valueRelativeToScale, columnHeight)
+}
+
+function getAbsoluteValue(relative, columnHeight) {
+ // we add some height to have some breathing room
+ const verticalPadding = columnHeight * config.temperatureScale.verticalPadding
+ const scaleHeight = columnHeight - 2 * verticalPadding
+ return scaleHeight * relative + verticalPadding
+}
+
+export function getTickPositions(columnHeight) {
+ 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)
+ tickPositions.push(position)
+ }
+ return tickPositions
+}
+
+export function getTickList(columnHeight) {
+
+ const units = unitObservable.value
+ const scaleMax = scaleObservable.value.max
+
+ return getTickPositions(columnHeight).map((tickPosition, i) => {
+
+ const tick = scaleMax - i * units
+ let isBold, label, shouldShowLabel
+
+ if (Number.isInteger(tick)) {
+ isBold = true
+ label = tick.toString() + '.0'
+ } else {
+ isBold = false
+ label = tick.toString()
+ }
+
+ // when temp range <= 3, units === 0.1 we show temp values with step 0.2
+ // when temp range > 3, units === 0.5 we show temp values with step 0.5
+
+ if (units === 0.1) {
+ // show label with step 0.2
+ shouldShowLabel = !(tick * 10 % 2)
+ } else {
+ // show label with step 0.5
+ shouldShowLabel = !(tick * 10 % 5)
+ }
+
+ return {
+ position: tickPosition,
+ label,
+ isBold,
+ shouldShowLabel,
+ }
+ })
+}