-
Notifications
You must be signed in to change notification settings - Fork 16
TidelineReact
This document is intended as the starting point for a discussion of how to best integrate tideline into an application/UI framework such as React.
Through late June 2014, the strategy for incorporating tideline into Tidepool's first application blip has been to define an application "page" PatientData
with a tideline Chart
as an embedded component. Many aspects of tideline's appearance are tracked in PatientData
's state, namely:
- the chart type (daily, weekly, or settings)
- the chart's current location in time
- whether the chart is located at the rightmost edge of the data (i.e., the most recent data)
- whether all smbg values are being shown or hidden in the two-week view
- whether the chart is currently undergoing an animated transition from one location in time to another
The Chart
component contains the code for generating and rendering all three of the chart types. Originally, the idea was to strive for API identity among all the chart types. In theory, all three charts should have identical or nearly identical capabilities in terms of navigation along the timeline - programmatic panning forward and backward, jumping to the most recent data, etc. However, as the visualization has developed, the APIs for each chart type have diverged, and each chart now contains functions that are unique to that chart type. For example, responding to message creation is exclusive to the one-day view, and hiding and showing values is exclusive to the two-week view. Furthermore, navigation along the timeline has not yet been implemented for the settings view.
An additional motivating factor behind creating just a single Chart
component instead of a component for each chart type was the fact that some data pre-processing was tied up with the setup of each chart, and so it was desirable to set up each chart only once and switch between (already created) charts on demand thereafter. This data pre-preprocessing has since been moved out into a tideline "plugin" (the preprocess
plugin), and thus there is no longer a need or desire to avoid repeated calls to set up a chart every time the user navigates between chart types.
In this repository's minimally functional example (contained in example/
), I have sketched out another strategy for rendering tideline's chart within a React framework.
The top-level Example
component is intended to be parallel to blip's PatientData
"page". This component receives as props chartData
, which is the patient data to be rendered (and the result of applying blip's preprocessing and tideline's preprocess
module to the data fetched from the Tidepool server), and imagesBaseUrl
, a URL pointing to where tideline can find the images it needs (for the notes icon, tooltips, etc.). These props are immutable.
In its state, the top-level Example
component tracks some additional mutable attributes:
-
chartPrefs
= an object containing default or user-defined preferences for rendering tideline charts, including (but not limited to) the units in which blood glucose values should be expressed (mg/dL or mmol/L) and (eventually) whether the timestamps in the data are to be displayed in a timezone (and if so, which) or if the rawdeviceTime
should be used for locating the data on the timeline -
chartType
= the type of chart to be rendered,daily
by default -
datetimeLocation
= the current location in time that the user is viewing on any of the chart types that support timeline navigation (currently, only the daily and weekly views)
chartPrefs
, chartType
, and datetimeLocation
are all tracked in Example
's state because they are mutable. Interactive elements within tideline can affect the chartPrefs
- for example, clicking to show the tabular display of basal schedules within the basal insulin pool will change the value of the chartPrefs
property hiddenPools
from {basalSettings: true}
to {basalSettings: false}
. In the future there may be additional interactive elements in Example
/PatientData
that mutate chartPrefs
. For example, we could decide to employ a timezone picker on the PatientData
"page" similar to the picker in Apple's Calendar application:
The latter two properties of the three listed above - chartType
and datetimeLocation
- are mutable as a consequence of the user's navigation between chart types and along the timeline, where possible (i.e., at present, not in the settings view).
And finally, a fourth property is tracked in Example
's state: initialDatetimeLocation
. In contrast to datetimeLocation
, this property is not updated as the user navigates along the timeline within the charts that currently support such navigation. Instead, initialDatetimeLocation
is only updated upon navigation between chart types; it is passed to the daily
and weekly
charts as a(n immutable) prop.
Lastly, Example
passes various handlers to its sub-components. There are three handlers for switching between chart types (onSwitchTo
...) and two functions for updating the chartPrefs
and datetimeLocation
in Example
's state.
Concluding Questions
-
Some of the
chartPrefs
will be mutable withinExample
/PatientData
(e.g., the above-mentioned examples ofhiddenPools
andtimezone
), but some may be immutable within this component - that is, I find it easier to imagine a user setting their preference for viewing blood glucose data in mg/dL or mmol/L as part of their user/profile settings, so that preference may not be mutable within thePatientData
"page". Is it a bad idea, then, to mix immutable and mutable properties in a single object in the component's state? It keeps the number of things tracked in the state reasonably small, but it obscures the sub-categories of immutable vs. mutable contained within. -
Tracking both
initialDatetimeLocation
anddatetimeLocation
feels like a hack, albeit a necessary hack since updatingdatetimeLocation
from the user's navigation along an embedded chart and passing it as a prop to the chart is a logical contradiction. Is there a more elegant way to handle this?
The higher-order daily chart component Daily
takes as props (from Example
's state) chartPrefs
, imagesBaseUrl
, patientData
, and (optionally) initialDatetimeLocation
as well as all five of the functions from Example
for switching between chart types and updating Example
's state.
The getInitialState
function returns an object with three properties initialized to default values:
{
atMostRecent: false,
inTransition: false,
title: ''
}
Daily
renders three sub-components: a Header
, a Footer
, and a DailyChart
. Having a higher-order chart component render the header and footer is one of the main advantages of this new proposal - the current implementation of blip has grown a tangle of if/else statements to control the varying requirements of the header and footer, which are dependent on the type of chart currently displayed.
The higher-level Daily
component also provides handler functions for interaction events (e.g., handleInTransition
, which is triggered when the chart is undergoing an animated transition from one datetime location to another). Each of the handler functions calls the appropriate function(s) in a sub-component accessible from Daily
's refs.
The embedded DailyChart
component is the component that actually renders the tideline chart. The props it takes are mostly familiar from the higher-level Daily
component - imagesBaseUrl
, initialDatetimeLocation
, and patientData
- but the chartPrefs
object has been broken down and only the properties actually needed for daily chart are passed as individual props - i.e., bgUnits
and hiddenPools
.
The necessary handler functions from Daily
are also passed as props. Most of these are bound directly to the appropriate event triggers, but onDatetimeLocationChange
gets called from DailyChart
's own handler (bound to the 'navigated'
event), which in addition to calling the function passed in as a prop for updating the datetimeLocation
in Example
's state also updates the datetimeLocation
in DailyChart
's local state. This is used to keep the chart in the same datetime location even when the chart re-renders due to tideline-internal interaction, such as choosing to hide or show the tabular display of basal schedules in the basal insulin pool.
The weekly chart works the same as the daily chart in most respects, with a higher-order Weekly
component that renders the Header
, Footer
, and WeeklyChart
sub-components. The only thing worth calling out specifically as an additional challenge in two-week view is the interaction between the showing and hiding values functionality and navigating to the most recent data via clicking the 'Most Recent' link in the header. As a result of the current limitations of the tideline API, it's not possible to keep values shown upon navigating to the most recent data, so handleClickMostRecent
has to set showingValues: false
in the state in order to have the footer link updated from 'Hide Values' to 'Show Values'.
As soon as the tideline API is updated to allow for passing in an option to default values to either hidden or shown in two-week view, it will be possible to add a property showingValuesWeekly
to the chartPrefs
object initialized in Example
/PatientData
's state and passed to Weekly
and then down to WeeklyChart
as a prop.
The settings view is a bit simpler, at present, than the daily and weekly views since there's no navigation along the timeline. For parallelism, it uses the same higher-order Settings
and embedded Header
, Footer
, and SettingsChart
components.
The Header
component takes the chartType
and all necessary click handler functions for the links it contains as props, as well as all the properties tracked in each chart's state (i.e., atMostRecent
, inTransition
, and title
). It leverages these props to adjust the classes for the 'One Day', 'Two Weeks', and 'Settings' links, as well as the navigation arrows - making any or these active, inactive, or hidden as appropriate.
Note that some of the click handlers - namely, onClickBack
and onClickNext
- are optional props. This is because at present the settings view does not support navigation along the timeline, and so does not render back and next arrows.
The Header
component itself is stateless.
The Footer
component also takes the chartType
and necessary click handlers as props, as well as an (optional) prop reflecting whether values are currently being shown and hidden, which is relevant only for the two-week view.
The Footer
component is also stateless.