Skip to content

TidelineReact

Jana E. Beck edited this page Jun 28, 2014 · 3 revisions

Tideline and React

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.

Previous Strategy

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.

Proposal for a New Strategy

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.

Top-Level

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 raw deviceTime 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:

Apple Calendar Timezone Picker

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 within Example/PatientData (e.g., the above-mentioned examples of hiddenPools and timezone), 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 the PatientData "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 and datetimeLocation feels like a hack, albeit a necessary hack since updating datetimeLocation 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?

Daily Chart

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.

Weekly Chart

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.

Settings Chart

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.

Header

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.

Footer

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.