In one of our research projects, we had to plot the real-time Heart Rate (HR) of a user -- which was collected from a wearable device during course of their workout -- against time elapsed. This real-time plot showed the status of how well a user is progressing in a workout. To plot this graph, we utilized data visualization techniques. Data visualization is essential to give meaning to raw data. It helps the viewer quickly understand trends and tracking data, as well as aid data analysis. The data can be represented in a multitude of ways, including a flow diagram, pie chart, line graph, scatter plot, and time-series graph.

heart rate vs time
heart rate vs time

Approach

During the workout, the HR of the user (in bpm) is collected from the wearable device at different times and then saved onto a server. This data is then retrieved from the server and a real-time HR is plotted on the graph in the iPad App. Below is the dataflow diagram of the process, followed by a detailed explanation of how to retrieve the data.

approach visual
approach visual
Getting data from the server

To get the user HR data, the iPad sends a timestamp of the workout in a request to the server. For the first request, the timestamp is set to null in order to retrieve the HR data from the start of the workout to the current time. In every subsequent call, the timestamp parameter must be updated to the time when the previous call was requested from the server to ensure that only new data is requested from the server. The server then sends a list of the HR of the user with its corresponding timestamp.

Plotting the real-time HR graph - Challenges and tricks

The major challenge is generating a real-time graph; that is, plotting the HR data in such a way that the graph appears to progress with time. To do this, we plotted sections of the HR graph by using Timer, which allowed us to schedule requests to the server and receive data from only a specific duration of time. This then generated data points for only a small section of the HR graph, which can then be added to the existing graph so it looks like it is progressing with time.

We scheduled a Timer request to continuously receive the HR of the user at an interval of 10 seconds, which then supplies us with real-time data.

self.timer = NSTimer.scheduledTimerWithTimeInterval(10, target: self, selector: Selector("getActualHRData"), userInfo: nil, repeats: true)

We then have to take this server response and generate data points from the HR plot. Since the graph shows the progress of a user through the workout, the y-axis represents the HR data while the x-axis represents the time elapsed in seconds. The first timestamp, which becomes the first data point, is the first HR number from the initial response generated by Timer; this must then be used to calculate all subsequent x-coordinates. The first timestamp is subtracted from the rest of the timestamps to calculate the time elapsed, which then become the data points to be plotted.

In order to keep track of the progress of a user during a workout, we must generate an expected HR graph in addition to the real-time HR graph. This will provide the user with a reference to see any deviation from the expected HR, so that they can stay on track with their goals. To create this, we had to use Core Plot, which allowed us to draw two graphs simultaneously.

Plotting graphs using Core Plot 1.2

Before we could utilize Core Plot 1.2 for our graph, we had to merge it with Apple’s recently introduced language, Swift. To do this, we created a file named Bridging-Header.h and set in the XCode build settings.

We then used the following steps to create the graph:

Creating the graph

  1. Initialize a CPTXYGraph var graph = CPTXYGraph(frame: CGRectZero)
  2. Set text style and graph title var tts = CPTMutableTextStyle.textStyle() as CPTMutableTextStyle tts.fontSize = 14.0 tts.color = CPTColor(componentRed: 255.0, green: 255.0, blue: 255.0, alpha: 1.0) tts.fontName = "HelveticaNeue-Bold" self.graph.titleTextStyle = tts self.graph.title = "Heart Rate vs Time"
  3. Set up graph padding to accommodate graph on screen var tts = CPTMutableTextStyle.textStyle() as CPTMutableTextStyle tts.fontSize = 14.0 tts.color = CPTColor(componentRed: 255.0, green: 255.0, blue: 255.0, alpha: 1.0) tts.fontName = "HelveticaNeue-Bold" self.graph.titleTextStyle = tts self.graph.title = "Heart Rate vs Time"
  1. For a better user experience, a theme can also be added to graph using: self.graph.applyTheme(CPTTheme(named:kCPTStocksTheme))

Setting up the plot space

  1. Initialize the CPTXYPlotSpace var plotSpace = graph.defaultPlotSpace as CPTXYPlotSpace!
  2. Set range of axes var xRange = plotSpace.xRange.mutableCopy() as CPTMutablePlotRange var yRange = plotSpace.yRange.mutableCopy() as CPTMutablePlotRange xRange.setLocationFloat(Float(0)) xRange.setLengthFloat(Float(self.userWorkout!.lastExpectedWorkoutLog()!.elapsedTime)) yRange.setLocationFloat(Float(0)) yRange.setLengthFloat(Float(210)) plotSpace.xRange = xRange plotSpace.yRange = yRange
  3. Add plot space to graph graph.addPlotSpace(plotSpace)

Configuring the axes

  1. Set text style and formatter for labels var xts = CPTMutableTextStyle.textStyle() as CPTMutableTextStyle xts.color = CPTColor(componentRed: 255.0, green: 255.0, blue: 255.0, alpha: 1.0) var axisFormatter = NSNumberFormatter() axisFormatter.maximumFractionDigits = 0 axisFormatter.minimumIntegerDigits = 1
  2. Set labels for axes let axisSet = graph.axisSet as CPTXYAxisSet! let xAxis = axisSet.xAxis as CPTXYAxis! xAxis.axisTitle = CPTAxisTitle(text: "Elapsed Time (seconds)", textStyle: xts) xAxis.labelFormatter = axisFormatter let yAxis = axisSet.yAxis as CPTXYAxis! yAxis.axisTitle = CPTAxisTitle(text: "Heart Rate (BPM)", textStyle: xts) yAxis.labelFormatter = axisFormatter yAxis.titleOffset = 35.0
  3. Set interval at which ticks appear on axes xAxis.setMajorIntervalLength(Float((self.userWorkout!.lastExpectedWorkoutLog()!.elapsedTime)/self.numberOfTicks)) yAxis.setMajorIntervalLength(20)
  4. Add graph to graph view as hosted graph self.graphView.hostedGraph = self.graph

Creating two scatter plots: expected HR and real-time HR

  1. Initialize CPTScatterPlot for scatter plot var plot = CPTScatterPlot() plot.dataSource = self
  2. Add an identifier to each plot. This is crucial for setting data points of that plot plot.identifier = "actual";
  3. Set line style. Add interpolation to graph for Bezier curve interpolation var actualPlotStyle = plot.dataLineStyle.mutableCopy() as CPTMutableLineStyle actualPlotStyle.lineWidth = 2.0 actualPlotStyle.lineColor = CPTColor(CGColor: (UIColor.yellowColor().CGColor)) plot.dataLineStyle = actualPlotStyle plot.interpolation = CPTScatterPlotInterpolationCurved
  4. Add plots to graph self.graph.addPlot(plot)

Adding data points to each plot

Core Plot has two delegate methods to display the data points on the graph:

  1. numberOfRecordsForPlot: This method sets the number of data points the plot has func numberOfRecordsForPlot(plot: CPTPlot!) -> UInt { return UInt(self.userWorkout!.lastExpectedWorkoutLog()!.elapsedTime) }
  2. numberForPlot: This method returns the data point for each plot by using the plot identifier set above. If the identifier is set to "expected," the data points for the expected HR graph are returned; otherwise, the real-time HR of the user is returned func numberForPlot(plot: CPTPlot!, field fieldEnum: UInt, recordIndex idx: UInt) -> NSNumber! { if plot.identifier.description == "expected" { var workoutLog = self.userWorkout!.expectedWorkoutLogAtIndexOrLast(idx) if workoutLog == nil { return 0 } return (fieldEnum == UInt(CPTScatterPlotFieldX.value) ? workoutLog!.elapsedTime : workoutLog!.bpm) } else { // actual var workoutLog =self.userWorkout!.actualWorkoutLogAtIndexOrLast(idx) if workoutLog == nil { return 0 } return (fieldEnum == UInt(CPTScatterPlotFieldX.value) ? workoutLog!.elapsedTime : workoutLog!.bpm) } }

Demonstration

The following is a video demonstration of the process we used above to plot the HR of a user that has been collected from a wearable device. As done above, the expected HR graph is plotted alongside the actual HR graph data, which allows the user to keep track of their progress. The graph should progress with time, thus generating a real-time plot.