Redesign chart

This commit is contained in:
Maria Zadnepryanets
2020-08-01 11:37:20 +00:00
committed by Sofiya Tepikin
parent 550b1e6314
commit ef16cfd041
27 changed files with 718 additions and 575 deletions
+22 -16
View File
@@ -1,32 +1,38 @@
import React from 'react'
import PropTypes from 'prop-types'
import { View } from 'react-native'
import { StyleSheet, View } from 'react-native'
import AppText from '../common/app-text'
import DripHomeIcon from '../../assets/drip-home-icons'
import styles from './styles'
import { cycleDayColor } from '../../styles'
import { Typography } from '../../styles/redesign'
import { CHART_YAXIS_WIDTH } from '../../config'
import { shared as labels } from '../../i18n/en/labels'
const ChartLegend = ({ xAxisHeight }) => {
const ChartLegend = ({ height }) => {
return (
<View style={[styles.yAxis, styles.chartLegend, {height: xAxisHeight}]}>
<DripHomeIcon
name="circle"
size={styles.yAxis.width - 7}
color={cycleDayColor}
/>
<AppText style={styles.yAxisLabels.dateLabel}>
{labels.date.toLowerCase()}
</AppText>
<View style={[styles.container, { height }]}>
<AppText style={styles.textBold}>#</AppText>
<AppText style={styles.text}>{labels.date}</AppText>
</View>
)
}
ChartLegend.propTypes = {
xAxisHeight: PropTypes.number.isRequired
height: PropTypes.number.isRequired
}
const styles = StyleSheet.create({
container: {
alignItems: 'center',
justifyContent: 'flex-end',
width: CHART_YAXIS_WIDTH
},
text: {
...Typography.label
},
textBold: {
...Typography.labelBold
}
})
export default ChartLegend
+11 -11
View File
@@ -3,20 +3,16 @@ import PropTypes from 'prop-types'
import { Shape } from 'react-native/Libraries/ART/ReactNativeART'
import styles from './styles'
import { Colors } from '../../styles/redesign'
import { CHART_STROKE_WIDTH, CHART_GRID_LINE_HORIZONTAL_WIDTH } from '../../config'
const ChartLine = ({ path, isNfpLine = false }) => {
const strokeStyle =
isNfpLine ? styles.nfpLine.stroke : styles.column.stroke.color
const strokeWidth =
isNfpLine ? styles.nfpLine.strokeWidth : styles.column.stroke.width
const ChartLine = ({ path, isNfpLine }) => {
const color = isNfpLine ? Colors.orange : Colors.grey
const width = isNfpLine
? CHART_STROKE_WIDTH : CHART_GRID_LINE_HORIZONTAL_WIDTH
return (
<Shape
stroke={strokeStyle}
strokeWidth={strokeWidth}
d={path}
/>
<Shape d={path} stroke={color} strokeWidth={width} />
)
}
@@ -25,4 +21,8 @@ ChartLine.propTypes = {
isNfpLine: PropTypes.bool,
}
ChartLine.defaultProps = {
isNfpLine: false
}
export default ChartLine
+112 -60
View File
@@ -1,26 +1,33 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { View, FlatList, ActivityIndicator } from 'react-native'
import { ActivityIndicator, FlatList, StyleSheet, View } from 'react-native'
import NoData from './no-data'
import AppLoadingView from '../common/app-loading'
import YAxis from './y-axis'
import nfpLines from './nfp-lines'
import AppPage from '../common/app-page'
import AppText from '../common/app-text'
import DayColumn from './day-column'
import HorizontalGrid from './horizontal-grid'
import AppText from '../common/app-text'
import NoData from './no-data'
import Tutorial from './tutorial'
import YAxis from './y-axis'
import { connect } from 'react-redux'
import { navigate } from '../../slices/navigation'
import { getCycleDaysSortedByDate } from '../../db'
import nothingChanged from '../../db/db-unchanged'
import { scaleObservable } from '../../local-storage'
import { makeColumnInfo } from '../helpers/chart'
import { getChartFlag, scaleObservable, setChartFlag } from '../../local-storage'
import { makeColumnInfo, nfpLines } from '../helpers/chart'
import config from '../../config'
import {
CHART_COLUMN_WIDTH,
SYMPTOMS,
CHART_SYMPTOM_HEIGHT_RATIO,
CHART_XAXIS_HEIGHT_RATIO
} from '../../config'
import { shared } from '../../i18n/en/labels'
import styles from './styles'
import { Colors, Spacing } from '../../styles/redesign'
class CycleChart extends Component {
static propTypes = {
@@ -36,11 +43,35 @@ class CycleChart extends Component {
this.getFhmAndLtlInfo = nfpLines()
this.shouldShowTemperatureColumn = false
this.checkShouldShowHint()
this.prepareSymptomData()
}
componentWillUnmount() {
this.cycleDaysSortedByDate.removeListener(this.handleDbChange)
this.removeObvListener()
}
checkShouldShowHint = async () => {
const flag = await getChartFlag()
const shouldShowHint = flag === 'true' ? true : false
this.setState({ shouldShowHint })
}
setShouldShowHint = async () => {
await setChartFlag()
this.setState({ shouldShowHint: false })
}
onLayout = ({ nativeEvent }) => {
if (this.state.chartHeight) return false
this.reCalculateChartInfo(nativeEvent)
this.updateListeners(this.reCalculateChartInfo)
}
prepareSymptomData = () => {
this.symptomRowSymptoms = config.symptoms.filter((symptomName) => {
this.symptomRowSymptoms = SYMPTOMS.filter((symptomName) => {
return this.cycleDaysSortedByDate.some(cycleDay => {
return cycleDay[symptomName]
})
@@ -71,33 +102,21 @@ class CycleChart extends Component {
reCalculateChartInfo = (nativeEvent) => {
const { height, width } = nativeEvent.layout
const xAxisCoefficient = this.shouldShowTemperatureColumn ?
config.xAxisHeightPercentage : config.xAxisHeightPercentageLarge
const symptomCoefficient = this.shouldShowTemperatureColumn ?
config.symptomHeightPercentage : config.symptomHeightPercentageLarge
this.xAxisHeight = height * xAxisCoefficient
this.xAxisHeight = height * CHART_XAXIS_HEIGHT_RATIO
const remainingHeight = height - this.xAxisHeight
this.symptomHeight = remainingHeight * symptomCoefficient
this.symptomHeight = remainingHeight * CHART_SYMPTOM_HEIGHT_RATIO
this.symptomRowHeight = this.symptomRowSymptoms.length *
this.symptomHeight
this.columnHeight = remainingHeight - this.symptomRowHeight
const chartHeight = this.shouldShowTemperatureColumn ?
height : (this.symptomRowHeight + this.xAxisHeight)
const numberOfColumnsToRender = Math.round(width / config.columnWidth)
const numberOfColumnsToRender = Math.round(width / CHART_COLUMN_WIDTH)
const columns = makeColumnInfo()
this.setState({ columns, chartHeight, numberOfColumnsToRender })
}
onLayout = ({ nativeEvent }) => {
if (this.state.chartHeight) return
this.reCalculateChartInfo(nativeEvent)
this.updateListeners(this.reCalculateChartInfo)
}
updateListeners(dataUpdateHandler) {
// remove existing listeners
if(this.handleDbChange) {
@@ -114,38 +133,47 @@ class CycleChart extends Component {
this.removeObvListener = scaleObservable(dataUpdateHandler, false)
}
componentWillUnmount() {
this.cycleDaysSortedByDate.removeListener(this.handleDbChange)
this.removeObvListener()
}
render() {
const { chartHeight, chartLoaded, numberOfColumnsToRender } = this.state
const shouldShowChart = this.chartSymptoms.length > 0 ? true : false
const {
chartHeight,
chartLoaded,
shouldShowHint,
numberOfColumnsToRender
} = this.state
const hasDataToDisplay = this.chartSymptoms.length > 0
return (
<View onLayout={this.onLayout} style={styles.container}>
{!shouldShowChart && <NoData navigate={this.props.navigate}/>}
{shouldShowChart && !chartHeight && !chartLoaded && <AppLoadingView />}
<AppPage
contentContainerStyle={styles.pageContainer}
onLayout={this.onLayout}
scrollViewStyle={styles.page}
>
{!hasDataToDisplay && <NoData />}
{hasDataToDisplay && !chartHeight && !chartLoaded && <AppLoadingView />}
<View style={styles.chartContainer}>
{shouldShowChart && (
{shouldShowHint && chartLoaded &&
<Tutorial onClose={this.setShouldShowHint} />
}
{hasDataToDisplay && chartLoaded &&
!this.shouldShowTemperatureColumn &&
<View style={styles.centerItem}>
<AppText style={styles.warning}>
{shared.noTemperatureWarning}
</AppText>
</View>
}
{hasDataToDisplay && (
<View style={styles.chartArea}>
{chartHeight && chartLoaded && (
<React.Fragment>
<YAxis
height={this.columnHeight}
symptomsToDisplay={this.symptomRowSymptoms}
symptomsSectionHeight={this.symptomRowHeight}
shouldShowTemperatureColumn=
{this.shouldShowTemperatureColumn}
xAxisHeight={this.xAxisHeight}
/>
{this.shouldShowTemperatureColumn && (<HorizontalGrid
height={this.columnHeight}
startPosition={this.symptomRowHeight}
/>)}
</React.Fragment>
<YAxis
height={this.columnHeight}
symptomsToDisplay={this.symptomRowSymptoms}
symptomsSectionHeight={this.symptomRowHeight}
shouldShowTemperatureColumn=
{this.shouldShowTemperatureColumn}
xAxisHeight={this.xAxisHeight}
/>
)}
{chartHeight &&
@@ -162,27 +190,28 @@ class CycleChart extends Component {
onEndReached={() => this.setState({end: true})}
ListFooterComponent={<LoadingMoreView end={this.state.end}/>}
updateCellsBatchingPeriod={800}
contentContainerStyle={{height: chartHeight}}
contentContainerStyle={{ height: chartHeight }}
/>
}
{chartHeight && chartLoaded && (
<React.Fragment>
{this.shouldShowTemperatureColumn &&
<HorizontalGrid height={this.columnHeight} />
}
</React.Fragment>
)}
</View>
)}
</View>
{shouldShowChart && chartLoaded && !this.shouldShowTemperatureColumn
&& (
<View style={styles.centerItem}>
<AppText style={{textAlign: 'center'}}>{shared.noTemperatureWarning}</AppText>
</View>
)}
</View>
</AppPage>
)
}
}
function LoadingMoreView({ end }) {
return (
<View style={styles.loadingMore}>
{!end && <ActivityIndicator size={'large'} color={'white'}/>}
<View style={styles.loadingContainer}>
{!end && <ActivityIndicator size={'large'} color={Colors.orange}/>}
</View>
)
}
@@ -191,6 +220,29 @@ LoadingMoreView.propTypes = {
end: PropTypes.bool
}
const styles = StyleSheet.create({
chartArea: {
flexDirection: 'row'
},
chartContainer: {
flexDirection: 'column'
},
loadingContainer: {
height: '100%',
backgroundColor: Colors.tourquiseLight,
justifyContent: 'center'
},
page: {
marginVertical: Spacing.small
},
pageContainer: {
paddingHorizontal: Spacing.base
},
warning: {
padding: Spacing.large
}
})
const mapDispatchToProps = (dispatch) => {
return({
navigate: (page) => dispatch(navigate(page)),
+36 -15
View File
@@ -1,32 +1,33 @@
import React from 'react'
import PropTypes from 'prop-types'
import { Text, View } from 'react-native'
import moment from 'moment'
import { StyleSheet, View } from 'react-native'
import { LocalDate } from 'js-joda'
import moment from 'moment'
import AppText from '../common/app-text'
import styles from './styles'
import cycleModule from '../../lib/cycle'
import { dateEnding } from '../helpers/home'
import { Containers, Typography } from '../../styles/redesign'
const CycleDayLabel = ({ height, date }) => {
const { label } = styles.column
const dayDate = LocalDate.parse(date)
const cycleDayNumber = cycleModule().getCycleDayNumber(date)
const isFirstDayOfMonth = dayDate.dayOfMonth() === 1
const dateFormatting = isFirstDayOfMonth ? 'MMM' : 'Do'
const dateFormatting = isFirstDayOfMonth ? 'MMM' : 'D'
const shortDate = moment(date, "YYYY-MM-DD").format(dateFormatting)
const boldDateLabel = isFirstDayOfMonth ? {fontWeight: 'bold'} : {}
const ending = isFirstDayOfMonth ?
'' : dateEnding[this.cycleDayNumber] || dateEnding['default']
const cycleDayLabel = cycleDayNumber ? cycleDayNumber : ' '
return (
<View style={[styles.chartLegend, { height }]}>
<Text style={label.number}>
{cycleDayNumber ? cycleDayNumber : ' '}
</Text>
<Text style={[label.date, boldDateLabel]}>
{shortDate}
</Text>
<View style={[styles.container, { height }]}>
<AppText style={styles.textBold}>{cycleDayLabel}</AppText>
<View style={{flexDirection: 'row', justifyContent: 'space-around', alignItems: 'center'}}>
<AppText style={styles.text}>{shortDate}</AppText>
<AppText style={styles.textLight}>{ending}</AppText>
</View>
</View>
)
}
@@ -36,4 +37,24 @@ CycleDayLabel.propTypes = {
date: PropTypes.string,
}
const styles = StyleSheet.create({
container: {
alignItems: 'flex-start',
justifyContent: 'flex-end',
left: 4,
},
containerRow: {
...Containers.rowContainer
},
text: {
...Typography.label
},
textBold: {
...Typography.labelBold
},
textLight: {
...Typography.labelLight
}
})
export default CycleDayLabel
+12 -12
View File
@@ -92,6 +92,18 @@ class DayColumn extends Component {
activeOpacity={1}
>
{shouldShowTemperatureColumn && <TemperatureColumn
horizontalLinePosition={this.fhmAndLtl.drawLtlAt}
isVerticalLine={this.fhmAndLtl.drawFhmLine}
data={this.data && this.data.temperature}
columnHeight={columnHeight}
/>}
<CycleDayLabel
height={xAxisHeight}
date={dateString}
/>
{ symptomRowSymptoms.map(symptom => {
const hasSymptomData = this.data.hasOwnProperty(symptom)
return (
@@ -107,18 +119,6 @@ class DayColumn extends Component {
}
)}
{shouldShowTemperatureColumn && <TemperatureColumn
horizontalLinePosition={this.fhmAndLtl.drawLtlAt}
isVerticalLine={this.fhmAndLtl.drawFhmLine}
data={this.data && this.data.temperature}
columnHeight={columnHeight}
/>}
<CycleDayLabel
height={xAxisHeight}
date={dateString}
/>
</TouchableOpacity>
)
}
+54 -40
View File
@@ -2,8 +2,14 @@ import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { Path, Shape } from 'react-native/Libraries/ART/ReactNativeART'
import styles from './styles'
import config from '../../config'
import { Colors } from '../../styles/redesign'
import {
CHART_COLUMN_WIDTH,
CHART_COLUMN_MIDDLE,
CHART_DOT_RADIUS,
CHART_STROKE_WIDTH
} from '../../config'
export default class DotAndLine extends Component {
static propTypes = {
@@ -20,48 +26,56 @@ export default class DotAndLine extends Component {
}
render() {
const y = this.props.y
const exclude = this.props.exclude
let lineToRight
let lineToLeft
const {
exclude,
leftTemperatureExclude,
leftY,
rightTemperatureExclude,
rightY,
y
} = this.props
let excludeLeftLine, excludeRightLine, lineLeft, lineRight
if (this.props.leftY) {
const middleY = ((this.props.leftY - y) / 2) + y
const excludedLine = this.props.leftTemperatureExclude || exclude
lineToLeft = makeLine(y, middleY, 0, excludedLine)
if (leftY) {
const middleY = ((leftY - y) / 2) + y
excludeLeftLine = leftTemperatureExclude || exclude
lineLeft = new Path()
.moveTo(CHART_COLUMN_MIDDLE - CHART_DOT_RADIUS, y)
.lineTo(0, middleY)
}
if (this.props.rightY) {
const middleY = ((y - this.props.rightY) / 2) + this.props.rightY
const excludedLine = this.props.rightTemperatureExclude || exclude
lineToRight = makeLine(y, middleY, config.columnWidth, excludedLine)
if (rightY) {
const middleY = ((y - rightY) / 2) + rightY
excludeRightLine = rightTemperatureExclude || exclude
lineRight = new Path()
.moveTo(CHART_COLUMN_MIDDLE + CHART_DOT_RADIUS, y)
.lineTo(CHART_COLUMN_WIDTH, middleY)
}
const dotStyle = exclude ? styles.curveDotsExcluded : styles.curveDots
const radius = dotStyle.r
const dot = (
<Shape
d={new Path()
.moveTo(config.columnMiddle, y - radius)
.arc(0, radius * 2, radius)
.arc(0, radius * -2, radius)
}
fill={dotStyle.fill}
key='dot'
/>
const dot = new Path().moveTo(CHART_COLUMN_MIDDLE , y - CHART_DOT_RADIUS)
.arc(0, CHART_DOT_RADIUS * 2, CHART_DOT_RADIUS)
.arc(0, CHART_DOT_RADIUS * -2, CHART_DOT_RADIUS)
const dotColor = exclude ? Colors.tourquise : Colors.tourquiseDark
const lineColorLeft = excludeLeftLine ?
Colors.tourquise : Colors.tourquiseDark
const lineColorRight = excludeRightLine ?
Colors.tourquise : Colors.tourquiseDark
return(
<React.Fragment>
<Shape
d={lineLeft}
stroke={lineColorLeft}
strokeWidth={CHART_STROKE_WIDTH}
key={y}
/>
<Shape
d={lineRight}
stroke={lineColorRight}
strokeWidth={CHART_STROKE_WIDTH}
key={y + CHART_DOT_RADIUS}
/>
<Shape d={dot} stroke={dotColor} strokeWidth={CHART_STROKE_WIDTH} key='dot' />
</React.Fragment>
)
return [lineToLeft, lineToRight, dot]
}
}
function makeLine(currY, middleY, x, excludeLine) {
const lineStyle = excludeLine ? styles.curveExcluded : styles.curve
return <Shape
stroke={lineStyle.stroke}
d={new Path()
.moveTo(config.columnMiddle, currY)
.lineTo(x, middleY)
}
key={x.toString()}
/>
}
+17 -10
View File
@@ -1,26 +1,33 @@
import React from 'react'
import PropTypes from 'prop-types'
import { View } from 'react-native'
import { StyleSheet, View } from 'react-native'
import { getTickPositions } from '../helpers/chart'
import styles from './styles'
import { Colors } from '../../styles/redesign'
import { CHART_GRID_LINE_HORIZONTAL_WIDTH, CHART_YAXIS_WIDTH } from '../../config'
const HorizontalGrid = ({ height, startPosition }) => {
const HorizontalGrid = ({ height }) => {
return getTickPositions(height).map(tick => {
return (
<View
top={startPosition + tick}
{...styles.horizontalGrid}
key={tick}
/>
<View key={tick} top={tick} {...styles.line}/>
)
})
}
HorizontalGrid.propTypes = {
height: PropTypes.number,
startPosition: PropTypes.number,
height: PropTypes.number
}
const styles = StyleSheet.create({
line: {
borderStyle: 'solid',
borderBottomColor: Colors.grey,
borderBottomWidth: CHART_GRID_LINE_HORIZONTAL_WIDTH,
left: CHART_YAXIS_WIDTH,
position:'absolute',
right: 0
}
})
export default HorizontalGrid
-85
View File
@@ -1,85 +0,0 @@
import { getCycleStatusForDay } from '../../lib/sympto-adapter'
import { normalizeToScale } from '../helpers/chart'
export default function () {
const cycle = {
status: null
}
function updateCurrentCycle(dateString) {
// for the NFP lines, we don't care about potentially extending the
// preOvu phase, so we don't include all earlier cycles, as that is
// an expensive db operation at the moment
cycle.status = getCycleStatusForDay(
dateString, { excludeEarlierCycles: true }
)
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, columnHeight) {
const ret = {
drawLtlAt: null,
drawFhmLine: false
}
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, columnHeight)
}
}
return ret
}
}
+28 -15
View File
@@ -1,31 +1,44 @@
import React from 'react'
import { StyleSheet, View } from 'react-native'
import PropTypes from 'prop-types'
import { View } from 'react-native'
import AppText from '../common/app-text'
import SettingsButton from '../settings/shared/settings-button'
import Button from '../common/button'
import { connect } from 'react-redux'
import { navigate } from '../../slices/navigation'
import { Containers } from '../../styles/redesign'
import { shared } from '../../i18n/en/labels'
import styles from './styles'
const NoData = ({ navigate }) => {
return (
<View flex={1}>
<View style={styles.centerItem}>
<AppText>{shared.noDataWarning}</AppText>
<SettingsButton
onPress={() => {navigate('CycleDay')}}
style={{marginHorizontal: 40}}
>
{shared.noDataButtonText}
</SettingsButton>
</View>
<View style={styles.container}>
<AppText>{shared.noDataWarning}</AppText>
<Button isCTA onPress={() => {navigate('CycleDay')}}>
{shared.noDataButtonText}
</Button>
</View>
)
}
NoData.propTypes = {
navigate: PropTypes.func,
navigate: PropTypes.func.isRequired,
}
export default NoData
const styles = StyleSheet.create({
container: {
...Containers.centerItems
}
})
const mapDispatchToProps = (dispatch) => {
return({
navigate: (page) => dispatch(navigate(page)),
})
}
export default connect(
null,
mapDispatchToProps,
)(NoData)
-174
View File
@@ -1,174 +0,0 @@
import config from '../../config'
import { shadesOfRed, cycleDayColor } from '../../styles/index'
const colorTemperature = '#765285'
const colorTemperatureLight = '#a67fb5'
export const dotRadius = 5
const lineWidth = 1.5
const colorLtl = '#feb47b'
const gridColor = '#d3d3d3'
const gridLineWidthVertical = 0.6
const gridLineWidthHorizontal = 0.3
const numberLabelFontSize = 13
const redColor = '#c3000d'
const violetColor = '#6a7b98'
const shadesOfViolet = ['#e3e7ed', '#c8cfdc', '#acb8cb', '#91a0ba', '#7689a9', 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 = {
container: { flex: 1 },
chartContainer: { flexDirection: 'column' },
chartArea: { flexDirection: 'row' },
centerItem: {
flex:1,
alignItems: 'center',
justifyContent: 'center',
marginHorizontal: 25,
},
curve: {
stroke: colorTemperature,
strokeWidth: lineWidth,
},
curveExcluded: {
stroke: colorTemperatureLight,
strokeWidth: lineWidth
},
curveDots: {
fill: colorTemperature,
r: dotRadius
},
curveDotsExcluded: {
fill: colorTemperatureLight,
r: dotRadius
},
column: {
label: {
date: {
color: 'grey',
fontSize: 9,
fontWeight: '100',
textAlign: 'center',
paddingTop: 2.5
},
number: {
color: cycleDayColor,
fontSize: numberLabelFontSize,
textAlign: 'center',
}
},
stroke: {
color: gridColor,
width: gridLineWidthVertical,
}
},
symptomDot: {
width: 12,
height: 12,
borderRadius: 50,
},
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,
borderRightWidth: 1,
borderColor: 'lightgrey',
borderStyle: 'solid'
},
yAxisLabels: {
tempScale: {
position: 'absolute',
right: 2,
color: 'grey',
fontSize: 9,
textAlign: 'left'
},
cycleDayLabel: {
textAlign: 'center',
justifyContent: 'center',
fontSize: Math.ceil(numberLabelFontSize / 2)
},
dateLabel: {
textAlign: 'center',
justifyContent: 'center',
color: 'grey',
fontSize: 9,
fontWeight: '100',
}
},
symptomIcon: {
alignItems: 'center',
justifyContent: 'center',
},
chartLegend: {
alignItems: 'center',
justifyContent: 'flex-end',
},
boldTick: {
fontWeight: 'bold',
fontSize: 11,
},
horizontalGrid: {
position:'absolute',
borderStyle: 'solid',
borderBottomColor: gridColor,
borderBottomWidth: gridLineWidthHorizontal,
width: '100%',
left: config.columnWidth
},
nfpLine: {
stroke: colorLtl,
strokeWidth: lineWidth,
},
symptomRow: {
alignItems: 'center',
justifyContent: 'center',
},
loadingMore: {
height: '100%',
backgroundColor: 'lightgrey',
justifyContent: 'center'
}
}
export default styles
+26 -15
View File
@@ -1,9 +1,13 @@
import React from 'react'
import PropTypes from 'prop-types'
import { View } from 'react-native'
import { StyleSheet, View } from 'react-native'
import styles from './styles'
import config from '../../config'
import { Colors, Containers } from '../../styles/redesign'
import {
CHART_COLUMN_WIDTH,
CHART_DOT_RADIUS,
CHART_GRID_LINE_HORIZONTAL_WIDTH
} from '../../config'
const SymptomCell = ({
height,
@@ -13,29 +17,23 @@ const SymptomCell = ({
}) => {
const shouldDrawDot = symptomValue !== false
const styleParent = [styles.symptomRow, { height, width: config.columnWidth }]
let styleChild
const styleCell = [styles.cell, { height, width: CHART_COLUMN_WIDTH }]
let styleDot
if (shouldDrawDot) {
const styleSymptom = styles.iconColors[symptom]
const styleSymptom = Colors.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
}]
styleDot = [styles.dot, { backgroundColor, borderColor, borderWidth }]
}
return (
<View style={styleParent} key={symptom}>
{shouldDrawDot && <View style={styleChild} />}
<View style={styleCell} key={symptom}>
{shouldDrawDot && <View style={styleDot} />}
</View>
)
}
@@ -50,4 +48,17 @@ SymptomCell.propTypes = {
isSymptomDataComplete: PropTypes.bool,
}
const styles = StyleSheet.create({
cell: {
backgroundColor: 'white',
borderColor: Colors.greyLight,
borderWidth: CHART_GRID_LINE_HORIZONTAL_WIDTH,
...Containers.centerItems
},
dot: {
width: CHART_DOT_RADIUS * 2,
height: CHART_DOT_RADIUS * 2,
borderRadius: 50
}
})
export default SymptomCell
+12 -5
View File
@@ -1,18 +1,19 @@
import React from 'react'
import PropTypes from 'prop-types'
import { View } from 'react-native'
import { StyleSheet , View } from 'react-native'
import DripIcon from '../../assets/drip-icons'
import styles from './styles'
import { Colors, Containers } from '../../styles/redesign'
import { CHART_YAXIS_WIDTH, CHART_ICON_SIZE } from '../../config'
const SymptomIcon = ({ symptom, height }) => {
return (
<View style={styles.symptomIcon} width={styles.yAxis.width} height={height}>
<View style={styles.container} width={CHART_YAXIS_WIDTH} height={height}>
<DripIcon
size={16}
size={CHART_ICON_SIZE}
name={`drip-icon-${symptom}`}
color={styles.iconColors[symptom].color}
color={Colors.iconColors[symptom].color}
/>
</View>
)
@@ -23,4 +24,10 @@ SymptomIcon.propTypes = {
symptom: PropTypes.string,
}
const styles = StyleSheet.create({
container: {
...Containers.centerItems
}
})
export default SymptomIcon
+16 -9
View File
@@ -1,13 +1,13 @@
import React from 'react'
import PropTypes from 'prop-types'
import { StyleSheet } from 'react-native'
import { Surface , Path } from 'react-native/Libraries/ART/ReactNativeART'
import ChartLine from './chart-line'
import DotAndLine from './dot-and-line'
import styles from './styles'
import config from '../../config'
import { CHART_COLUMN_WIDTH, CHART_STROKE_WIDTH } from '../../config'
const TemperatureColumn = ({
horizontalLinePosition,
@@ -15,20 +15,21 @@ const TemperatureColumn = ({
data,
columnHeight
}) => {
const x = styles.nfpLine.strokeWidth / 2
const x = CHART_STROKE_WIDTH / 2
return (
<Surface width={config.columnWidth} height={columnHeight}>
<Surface
width={CHART_COLUMN_WIDTH}
height={columnHeight}
style={styles.container}
>
<ChartLine
path={new Path().lineTo(0, columnHeight)}
/>
<ChartLine path={new Path().lineTo(0, columnHeight)}/>
{horizontalLinePosition && <ChartLine
path={new Path()
.moveTo(0, horizontalLinePosition)
.lineTo(config.columnWidth, horizontalLinePosition)
.lineTo(CHART_COLUMN_WIDTH, horizontalLinePosition)
}
isNfpLine={true}
key='ltl'
@@ -61,4 +62,10 @@ TemperatureColumn.propTypes = {
columnHeight: PropTypes.number,
}
const styles = StyleSheet.create({
container: {
backgroundColor: 'white'
}
})
export default TemperatureColumn
+25 -17
View File
@@ -1,29 +1,31 @@
import React from 'react'
import PropTypes from 'prop-types'
import { View } from 'react-native'
import { StyleSheet, View } from 'react-native'
import Tick from './tick'
import { getTickList } from '../helpers/chart'
import styles from './styles'
const TickList = ({ height }) => {
return (
<View style={[styles.yAxis, { height }]}>{
getTickList(height)
.map(({ label, position, isBold, shouldShowLabel}) => {
return (
<Tick
key={label}
yPosition={position}
isBold={isBold}
shouldShowLabel={shouldShowLabel}
label={label}
/>
)
})
}</View>
<View style={[styles.container, height]}>
{
getTickList(height)
.map(({ isBold, label, position, shouldShowLabel, tickHeight}) => {
return (
<Tick
height={tickHeight}
isBold={isBold}
key={label}
label={label}
shouldShowLabel={shouldShowLabel}
yPosition={position}
/>
)
})
}
</View>
)
}
@@ -31,4 +33,10 @@ TickList.propTypes = {
height: PropTypes.number,
}
const styles = StyleSheet.create({
container: {
flex: 1
}
})
export default TickList
+36 -12
View File
@@ -1,29 +1,53 @@
import React from 'react'
import { StyleSheet, View } from 'react-native'
import PropTypes from 'prop-types'
import AppText from '../common/app-text'
import styles from './styles'
import { Sizes } from '../../styles/redesign'
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
]
const Tick = ({ yPosition, height, isBold, shouldShowLabel, label }) => {
const top = yPosition - height / 2
const containerStyle = [ styles.container, { flexBasis: height, height, top }]
const textStyle = isBold ? styles.textBold : styles.textNormal
return <AppText style={style}>{shouldShowLabel && label}</AppText>
return(
<View style={containerStyle}>
<AppText style={textStyle}>{shouldShowLabel && label}</AppText>
</View>
)
}
Tick.propTypes = {
yPosition: PropTypes.number,
height: PropTypes.number.isRequired,
isBold: PropTypes.bool,
shouldShowLabel: PropTypes.bool,
label: PropTypes.string,
}
const text = {
right: 4,
lineHeight: Sizes.base,
textAlign: 'right',
textAlignVertical: 'center'
}
const styles = StyleSheet.create({
container: {
position: 'absolute',
right: 0,
width: 40
},
textBold: {
fontSize: Sizes.base,
fontWeight: 'bold',
...text
},
textNormal: {
fontSize: Sizes.small,
...text
}
})
export default Tick
+48
View File
@@ -0,0 +1,48 @@
import React from 'react'
import PropTypes from 'prop-types'
import { Image, StyleSheet, TouchableOpacity, View } from 'react-native'
import AppIcon from '../common/app-icon'
import AppText from '../common/app-text'
import { Colors, Containers, Sizes, Spacing } from '../../styles/redesign'
import { chart } from '../../i18n/en/labels'
const image = require('../../assets/swipe.png')
const Tutorial = ({ onClose }) => {
return (
<View style={styles.container}>
<Image resizeMode='contain' source={image} style={styles.image} />
<View style={styles.textContainer}>
<AppText>{chart.tutorial}</AppText>
</View>
<TouchableOpacity onPress={onClose} style={styles.iconContainer}>
<AppIcon name='cross' color={Colors.orange} />
</TouchableOpacity>
</View>
)
}
Tutorial.propTypes = {
onClose: PropTypes.func.isRequired
}
const styles = StyleSheet.create({
container: {
...Containers.rowContainer,
padding: Spacing.large
},
iconContainer: {
alignSelf: 'flex-start',
marginBottom: Sizes.base
},
image: {
height: 40
},
textContainer: {
width: '70%'
}
})
export default Tutorial
+10 -4
View File
@@ -1,12 +1,12 @@
import React from 'react'
import PropTypes from 'prop-types'
import { View } from 'react-native'
import { StyleSheet, View } from 'react-native'
import SymptomIcon from './symptom-icon'
import TickList from './tick-list'
import ChartLegend from './chart-legend'
import styles from './styles'
import { CHART_YAXIS_WIDTH } from '../../config'
const YAxis = ({
height,
@@ -19,6 +19,8 @@ const YAxis = ({
return (
<View>
{shouldShowTemperatureColumn && <TickList height={height} />}
<ChartLegend height={xAxisHeight} />
<View style={[styles.yAxis, {height: symptomsSectionHeight}]}>
{symptomsToDisplay.map(symptom => (
<SymptomIcon
@@ -29,8 +31,6 @@ const YAxis = ({
)
)}
</View>
{shouldShowTemperatureColumn && <TickList height={height} />}
<ChartLegend xAxisHeight={xAxisHeight} />
</View>
)
}
@@ -43,4 +43,10 @@ YAxis.propTypes = {
xAxisHeight: PropTypes.number.isRequired
}
const styles = StyleSheet.create({
yAxis: {
width: CHART_YAXIS_WIDTH
}
})
export default YAxis