Skip to content

TidelineReact

Jana E. Beck edited this page Jun 26, 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". Tracked in this component's state are:

  • chartData = the patient data to be rendered (the result of applying blip's preprocessing and tideline's preprocess module to the data fetched from the Tidepool server)
  • 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
  • imagesBaseUrl = a URL pointing to where tideline can find the images it needs
  • chartType = the type of chart to be rendered, daily by default

In the tideline example, all four of these attributes are tracked in Example's state, but in blip the chartData, chartPrefs, and imagesBaseUrl could be passed the PatientData "page" as (immutable) props. Only the chartType necessarily needs to be constantly updated in the state (upon switching between chart types). Possibly, chartPrefs might also need to be mutable, depending on where in the UI these preferences are determined. 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

On the other hand, 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".

An additional variable is set in Example's state: the initialDatetimeLocation for daily and weekly charts (and, eventually, the settings page, when we support navigation through the settings history) upon switching between these charts. (Since the default is to load the most recent twenty-four hours of data, this property does not need to be set in getInitialState but only when the user first navigates away from the default most recent daily chart.)

It is not possible to keep track of the chart's (whether daily, weekly, or settings) current datetime location in Example's state. The reason for this is that some of the functions in some of Example's components (namely: handleHideBasalSettings in Daily) destroy and then render a new tideline chart, which results in a situation where Example's state is triggered to be updated while it is still rendering its sub-components, a fatal hiccough in the world of React.

Concluding Questions

  • How hard will it be to adapt the current PatientData "page" into something like Example (plus the things required in blip for messages)? The biggest difference is that PatientData renders the header and footer separately from the Chart but Example renders one of three chart components, each of which contains a Header and Footer as sub-components.

  • Should all the components below the level of Example/PatientData (i.e., Daily, Weekly, Settings, Header, Footer) be part of the tideline codebase, living in plugins/blip/ alongside the chart factories?

  • What should be in PatientData's state, with respect to tideline? Should the chartPrefs be broken out into separate props or state properties early on, since there may be a desire to have some of these be mutable?

  • Is it a problem, thinking ahead to a future where we might like a user to be able to bookmark a particular chart view, that we can't track the current datetime location of a chart in PatientData? We can certainly work around this limitation with an internal bookmarking tool, but I'm not sure about browser-native bookmarking.

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 three functions: switchToDaily, switchToSettings, and switchToWeekly. Its getInitialState function returns an object with one property - hiddenPools - and sets basalSettings: true in that object.

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 which chart type is currently being 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 - bgUnits - plus the new property hiddenPools given a default key-value pair of basalSettings: true in the state of Daily.

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). In fact, the main reason for breaking DailyChart out as a separate component from Daily is to be able to properly handle the continuous updating of the datetime location title in the header without constantly tracking the current datetime location of the chart in Daily's state (which results in the above-mentioned fatal React error when trying to show or hide basal settings). Instead, only DailyChart tracks the current datetime location in its state (although this may be pointless), and it passes each new pair of datetimeLocationEndpoints up to Daily's handleDatetimeLocationChange via this function, passed as onDatetimeLocationChange in DailyChart's props. Then, handleDatetimeLocationChange calls the appropriate function in Header to update the title string reflecting the datetime location in the header.

Concluding Questions

  • Tracking the current datetime location in DailyChart is the one place we can do this without running into a fatal React error, but since it's not being used for anything in DailyChart (and since the state gets wiped out every time the component is unmounted), it's kind of...pointless, unless it could potentially be useful for updating the URL to provide bookmarking of a specific data view functionality.

  • Because the datetime location and the chart options specific to one-day view (i.e., hiddenPools) are tracked in DailyChart and Daily's state, respectively, both of these are wiped out when these components are unmounted, so if you navigate away from one-day view in blip (e.g., to settings) and then navigate back, you won't see the same view as when you left - the tabular basal settings pool will always be hidden, and you'll always be on one-day view at the most recent twenty-four hours of data. And since chartType is tracked in Example/PatientData's state and this is wiped out when navigating away from the patient data page (e.g., to your profile), you'll always come back to the default one-day view when you navigate back. Perhaps an application-wide chartState object is the only way to solve this?

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'.

Concluding Questions: Same as for Daily Chart.

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 SettingsChart components.

Header

The Header component takes the chartType and all necessary click handler functions for the links it contains as props. It leverages the chartType 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 to the chart type.

Header's state tracks whether the user is currently viewing the most recent data and whether the chart is currently undergoing an animated transition between datetime locations.

What Header's state does not (and cannot) track is the current datetime location of the graph. Instead of updating the title string of the header reflecting the current datetime location via Header's state, as would be most React-like, the updateTitle function inserts an updated title string (passed in as an argument) directly into the DOM using D3. This is, quite frankly, a hack. :(

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. Unlike the Header component, it contains no hacks! \o/

Clone this wiki locally