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 :-)
I want a graph displaying the sum of all reps i did for a certain excerice (aka tracker) on a daily basis.
<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.
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();
};
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);
});
};