Displaying KeepTrack Pro Trackers Graphs with Javascript

Last Update: 25.12.2014. By Jens in Javascript

I recently started using KeepTrack Pro for tracking my fitness training. Its a great little tool and pretty flexible. But i missed sum view in the graph section (which seem to come in version 4), but luckily it had an backup mode and when i took a look at the backup file, i felt the urge to hack something. And we are doing it with Javascript :-)

The goal

I want a graph displaying the sum of all reps i did for a certain excerice (aka tracker) on a daily basis.

Link to demo

The BackUp File Format

<watches>
    <watch name="Leg raise flat" initial_value="1" sorting="0" type="number" units="" showAvg="no" showMovAvg="no" movAvgPeriod="7" increment="1.0" calendar_display="SUM">
        <value time="1407996780">20</value>
        <value time="1407996780">19</value>
    </watch>
</watches>

Basically its a list of trackers called watch and each one does have a type. Right now we are only interested in the type number. Inside each watch is a list of values with a timestamp (unix) and the actual numerical value.

The Graph

I am using the the Rickshaw library for the graph. Its small, focused on time series graphs and easy to use. A starting point for learning is the Rickshaw Tutorial; i’ll skip that step here.

The HTML:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>KeepTrack Pro - Rickshaw Test</title>

    <script src="http://code.jquery.com/jquery-1.11.0.min.js"></script>
    <script src="vendor/d3.v3.js"></script> 
    <script src="vendor/rickshaw.js"></script>

    <script src="keeptrack.js"></script>

    <link type="text/css" rel="stylesheet" href="vendor/rickshaw.css">
    <link type="text/css" rel="stylesheet" href="keeptrack.css">
  </head>

  <body>
    <div id="chart_container">
      <div id="y_axis"></div>
      <div id="chart"></div>
      <div id="x_axis"></div>
    </div>
    <div id="legend"></div>

    <script>
      $(function(){ //DOM Ready
        loadTracker('demo.xml');
      });
      </script>
  </body>
</html>

The Javascript (See inline comments):

function render(seriesData) {
  var palette = new Rickshaw.Color.Palette();
  var theData = [];
  // prepare our data to the rickshaw format and assigne a color
  for(var key in seriesData) {
    theData.push({ data: seriesData[key], color :palette.color(), name: key});
  }
  //the rickshaw setup
  var graph = new Rickshaw.Graph( {
    element: document.querySelector("#chart"),
    width: 580,
    height: 200,
    renderer: 'line',
    series: theData
  } );
  var yAxis = new Rickshaw.Graph.Axis.Y({
    graph: graph,
    orientation: 'left',
    element: document.getElementById('y_axis')
  });
  var time = new Rickshaw.Fixtures.Time();
  var timeUnit = {
    name: 'day',
    seconds: 86400,
    formatter: function(d) { return d.getDate() + "." +  ( d.getMonth() + 1)+ "." + d.getFullYear()}
  };
  var xAxis = new Rickshaw.Graph.Axis.Time({
    graph: graph,
    timeUnit: timeUnit,
    orientation: 'bottom',
    element: document.getElementById('x_axis'),
  });
  var legend = new Rickshaw.Graph.Legend( {
    element: document.querySelector('#legend'),
    graph: graph
  } );
  var hoverDetail = new Rickshaw.Graph.HoverDetail( {
    graph: graph,
    xFormatter : function(x) {
      return new Date( x * 1000 ).toLocaleDateString();
    }
  } );
  var shelving = new Rickshaw.Graph.Behavior.Series.Toggle({
    graph: graph,
    legend: legend
  });
  var highlighter = new Rickshaw.Graph.Behavior.Series.Highlight({
    graph: graph,
    legend: legend
  });
  graph.render();
};

The Glue

We load the xml with jquery ajax, look for each tracker of type number and buidl daily sums of it. If a tracker has at least w days of values we render it.

function loadTracker(filename) {
  $.get(filename, function( data ) {
    var allSeries = {};
    $(data).find('watch').each(function(){
      if("number" === $(this).attr("type")) {
        var trackerData = {};
        $(this).find('value').each(function(){
          var d = new Date();
          d.setTime(parseInt($(this).attr("time")) * 1000);
          d.setHours(12, 0, 0, 0);
          var date = d.getTime();
          if(date in trackerData) {
            trackerData[date]+= parseInt($(this).text());
          } else {
            trackerData[date] = parseInt($(this).text());
          }
        });

        var sorted = getSortedKeys(trackerData);
        if(sorted.length > 1) {
          var seriesData = [];
          //add missing points
          addMissingPoints(trackerData, sorted);
          //sort again for rickshaw
          sorted = getSortedKeys(trackerData);
          for(var k in sorted) {
            var key = sorted[k];
            seriesData.push({x: parseInt(key) / 1000, y: trackerData[key]});
          }
          allSeries[$(this).attr("name")] = seriesData;
        }
      }
    });
    render(allSeries);
  });
};