// ugly get date class hacking Date.prototype.getWeek = function() { var onejan = new Date(this.getFullYear(),0,1); return Math.ceil((((this - onejan) / 86400000) + onejan.getDay()+1)/7); } var gfx = function(){ return{ chartStyles: { barChart: { width: 666 - 110 - 5, height: 77 - 20, margin: { left: 110, top: 20, bottom: 0, right: 5 } }, timeChart: { width: 200, height: 650, margin: { left: 0, top: 0, bottom: 0, right: 0 } }, bubbleChart: { width: 650, height: 10, margin: { left: 40, top: 0, bottom: 0, right: 0 } } }, shortMonths: [ 'Jan.', 'Feb.', 'Mar.', 'Apr.', 'May', 'June', 'July', 'Aug.', 'Sept.', 'Oct.', 'Nov.', 'Dec.' ], fullMonths: [ 'January', 'Febuary', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December' ], startWeek: null, endWeek: null, hackIntervalIn: true, minRadius: 1, maxRadius: 26.2, rowHeight: 90, rowsToStart: 10, dateCounter: 0, scaleBy: 'distance', startDate: new Date(2013,08,02), endDate: new Date(2013,08,08), init: function(user){ this.setup(); this.getData(user) this.tooltip = new Ink.Tooltip({ align: 'aboveCenter'}); }, bindEvents: function(){ var months = $('.g-month-title'); this.offsets = []; var me = this; months.each(function(d,i){ me.offsets.push({ y: $(this).offset().top -20, title: $(this).text(), interval: $(this).data('interval') }) }) var hs = _.debounce($.proxy(this.handleScroll, this), 0) $(window).scroll(hs) }, handleScroll: function(){ var y = $(window).scrollTop() this.offsets.forEach(function(o,i){ if(y >= o.y && i != gfx.offsets.length - 1){ if(y < gfx.offsets[i+1].y){ $('#row-summary').text(o.title) return; } } }) }, setup: function(){ // this.timeChart= d3.select("#g-barNav-chart").append("svg") // .attr("width", this.chartStyles.timeChart.width + this.chartStyles.timeChart.margin.left + this.chartStyles.timeChart.margin.right) // .attr("height", this.chartStyles.timeChart.height + this.chartStyles.timeChart.margin.top + this.chartStyles.timeChart.margin.bottom) // .append("g") // .attr("transform", "translate(" + this.chartStyles.timeChart.margin.left + "," + this.chartStyles.timeChart.margin.top + ")"); this.barChart = d3.select("#g-volume").append("svg") .attr("width", this.chartStyles.barChart.width + this.chartStyles.barChart.margin.left + this.chartStyles.barChart.margin.right) .attr("height", this.chartStyles.barChart.height + this.chartStyles.barChart.margin.top + this.chartStyles.barChart.margin.bottom) .append("g") .attr("transform", "translate(" + this.chartStyles.barChart.margin.left + "," + this.chartStyles.barChart.margin.top + ")"); }, getData: function(user){ var me = this; d3.json(user+'.json',function(data){ me.handleData(data) }); }, getDatesFromInterval: function(interval){ }, handleData: function(data){ var me = this; // this.formatDate = d3.time.format("%Y-%m-%dT%H:%M:%S-%H:%M"); this.formatDate = d3.time.format.utc("%Y-%m-%dT%H:%M:%S.%L+00:00"); this.intervalDate = d3.time.format("%Y\y%W\w"); data.forEach(function(d){ d.entry.structured_activities = [ [], [], [], [], [], [], [] ]; if(d.entry.activities.length > 0){ d.entry.activities.forEach(function(activity,n){ activity.formattedDate = new Date(0); activity.formattedDate.setUTCSeconds(activity.start_date - activity.utc_offset) activity.dayOfWeek = activity.formattedDate.getDay() activity.week = activity.formattedDate.getWeek() d.week = activity.week; activity.month_year = activity.formattedDate.getMonth()+'_'+activity.formattedDate.getFullYear() d.entry.structured_activities[activity.dayOfWeek].push(activity); }) } }); // //if(this.hackIntervalIn === true){ // c = this.startWeek; // data.forEach(function(d, i){ // data[i].week = c; // data[i].distance = 0 // data[i].moving_time = 0 // data[i].elev_gain = 0 // data[i].activities = [ // [], //0 // [], //1 // [], //2 // [], //3 // [], //4 // [], //5 // [] //6 // ] // c++; // if(d.length > 0){ // d.forEach(function(_d){ // _d.formattedDate = gfx.formatDate2.parse(_d.start_date_local); // _d.dayOfWeek = _d.formattedDate.getDay(); // _d.week = _d.formattedDate.getWeek(); // data[i].formattedDate = _d.formattedDate.getMonth() // data[i].activities[_d.dayOfWeek].push(_d); // // add it all up // data[i].distance += _d.distance; // data[i].moving_time += _d.moving_time; // data[i].elev_gain += _d.elev_gain; // }); // } // }); // } // console.log(_.pluck(data, 'week')) this.data = data.reverse(); var activities = _.map(this.data, function(d){ return d.entry.activities; }); this.activities = _.flatten(activities); this.activities = _.reject(this.activities, function(activity){ return activity.formattedDate.getFullYear() === 2012 }) this.dates = _.map(this.activities, function(d){ return d.formattedDate }) this.months = _.toArray(d3.nest() .key(function(d){ return d.month_year }) .map(this.activities)) this.races = _.toArray(d3.nest() .key(function(d){ return d.month_year }) .key(function(d){ return d.workout_type }) .map(_.select(this.activities, function(a){ return a.workout_type === 1 }))) this.setScales(); this.render(); }, setScales: function(){ var me = this; var intervals = _.pluck(this.data, 'interval') $('body').attr('class','g-scaleBy-'+gfx.scaleBy); this.timeYScale = d3.time.scale() .range([0, this.chartStyles.timeChart.height]) .domain(d3.extent(this.dates).reverse()) this.barXScale = d3.scale.ordinal() .rangeBands([this.chartStyles.barChart.margin.left, this.chartStyles.barChart.width]) .domain(intervals) this.barYScale = d3.scale.linear() .range([0,this.chartStyles.barChart.height]) .domain(d3.extent(this.data, function(d){ return d.distance })) .nice() this.barYAxisScale = d3.scale.linear() .range([this.chartStyles.barChart.height,0]) .domain(d3.extent(this.data, function(d){ return d.distance })) .nice() this.bubbleXScale = d3.scale.ordinal() .rangeBands([me.chartStyles.bubbleChart.margin.left, me.chartStyles.bubbleChart.width]) .domain([8,1,2,3,4,5,6,0]) ext = d3.extent(this.activities, function(d){ return d[gfx.scaleBy] }) if(this.scaleBy === 'distance'){ this.runScale = d3.scale.sqrt() .range([5, 26]) .domain([5, d3.max(this.activities, function(d){ return d[gfx.scaleBy] })]) }else{ this.runScale = d3.scale.sqrt() .range([this.minRadius, 26.2]) .domain([1, d3.max(this.activities, function(d){ return d[gfx.scaleBy] })]); } }, format_distance_value: function(val){ return this.getMile(val).toFixed(1) }, format_time_value: function(val){ // var totalSec = new Date().getTime() / 1000; var totalSec = val var hours = parseInt( totalSec / 3600 ) % 24; var minutes = parseInt( totalSec / 60 ) % 60; var seconds = totalSec % 60; var result = (hours < 10 ? hours+'hr' : hours+'hr') + " " + (minutes < 10 ? "0" + minutes+'min' : minutes+'min'); return result }, format_elev_gain_value: function(val){ return val.toString().replace('.0','') }, elev_gain_suffix: function(){ return 'ft' }, distance_suffix: function(){ return 'mi' }, time_suffix: function(){ return '' }, getMile: function(distance){ return distance * 0.000621371 }, getMeter: function(){ }, updateScales: function(){ this.setScales(); }, update: function(scaleBy){ var me = this; this.scaleBy = scaleBy; this.setScales() // this.barChart.selectAll('.weekBar') // .transition() // .duration(700) // .attr('width', function(d){ return me.barXScale(d[gfx.scaleBy]) }) var days = d3.selectAll('.dayWrap') d3.selectAll('.weekTotal').text(function(d){ if(d[gfx.scaleBy]) { return me['format_'+gfx.scaleBy+'_value'](d[gfx.scaleBy])+gfx[gfx.scaleBy+'_suffix']() }}) days.each(function(d,n){ if(d.length > 0){ var total = _(d).pluck(gfx.scaleBy).reduce(function(d1,d2){ return d1 + d2}) }else{ var total = 0; } var packSize = [me.runScale(total), me.runScale(total)]; // d3.select(this) // .attr('transform',function(__d){ return 'translate('+(me.bubbleXScale(n)-(packSize[0]/2))+','+(me.rowHeight - packSize[0] / 2) +')' }) if(total > 0){ pack = d3.layout.pack() .sort(d3.descending) .size(packSize) .value(function(__d){ if(gfx.scaleBy in __d){ return __d[gfx.scaleBy];} else{ return 0 } }) .children( function(__d,i){ return __d; } ) .radius(function(__d){ return me.runScale(__d) }) var runs = d3.select(this).selectAll('.run') .data(pack.nodes) d3.select(this).select('.sum.no-rest') .transition() .attr('transform', function(d){ return 'translate('+packSize[0]/2+','+(packSize[0] / 2) +')' }) .text(function(__d){ if(gfx.scaleBy === 'distance'){ var t = me.getMile(total).toFixed(1).toString() t = (t.indexOf('.0') > 0) ? t.replace('.0','') : t; return t }else{ return me['format_'+gfx.scaleBy+'_value'](__d.value)+gfx[gfx.scaleBy+'_suffix']() } }) runs.transition() .attr("transform", function(__d) { return "translate(" + __d.x + "," + (__d.y)+ ")"; }) .attr("r", function(__d){ return __d.r }) }else{ } }); // if(d.length > 0){ // var total = _(d).pluck(gfx.scaleBy).reduce(function(d1,d2){ return d1 + d2}) // }else{ // var total = 0; // } // var packSize = [me.runScale(total), me.runScale(total)]; // // d3.select(this) // // .attr('transform',function(__d){ return 'translate('+(me.bubbleXScale(n)-(packSize[0]/2))+','+(me.rowHeight - packSize[0] / 2) +')' }) // // d3.select(this) // // .append('rect') // // .attr('class',function(d){ return (total === 0) ? 'text-bg' : 'text-bg-hidden' }) // // .attr('width',40) // // .attr('height', 30) // // .attr('transform', function(d){ return 'translate(-18,-10)' }) // // .style('opacity',0) // if(total > 0){ // pack = d3.layout.pack() // .sort(d3.ascending) // .size(packSize) // .value(function(__d){ if(gfx.scaleBy in __d){ return __d[gfx.scaleBy];} else{ return 0 } }) // .children( // function(__d,i){ // return __d; // } // ) // .radius(function(__d){ // return me.runScale(__d) // }) // d3.selectAll('.sum') // .text(function(){ // if (total > 0){ // if(scaleBy === 'elev_gain'){ // return total+'ft.' // }else{ // var t = me.getMile(total).toFixed(1).toString() // t = (t.indexOf('.0') > 0) ? t.replace('.0','') : t; // return t // } // }else{ // return 'Rest' // } // }) // .attr('text-anchor', 'middle') // .attr('dy','4px') // .attr('transform', function(d){ return 'translate('+packSize[0]/2+','+(packSize[0] / 2) +')' }) // // .style('font-size',0) // // .style('opacity',0) // // text // d3.select(this).selectAll('.run') // .data(pack.nodes) // .enter() // .append('circle') // .attr('class', function(d){ return 'run run-depth-'+d.depth+' run-type-'+d.workout_type }) // .attr("transform", function(d) { return "translate(" + d.x + "," + (d.y)+ ")"; }) // .attr("r", 0) // transition = me.bubbleChart.transition().duration(700).delay(0) // transition.selectAll('.run') // .transition() // .attr("r", function(d,i){ // return d.r; // }) // .attr("transform", function(d) { return "translate(" + d.x + "," + (d.y)+ ")"; }) // } // }) // // // d3.select(this).selectAll('.run') // .data(pack.nodes) // .enter() // .append('circle') // .attr('class', function(d){ return 'run run-depth-'+d.depth }) // .attr("transform", function(d) { return "translate(" + d.x + "," + (d.y)+ ")"; }) // .attr("r", 0) // }); // transition = this.bubbleChart.transition().duration(1000).delay(0) // transition.selectAll('.run') // .update() // .transition() // .attr("r", function(d,i){ // return d.r; // }) }, render: function(){ this.renderBarChart(); this.renderTimeChart(); // rows = $('#g-rows'); var bubble = this.chartStyles.bubbleChart; // this.bubbleChart = d3.select('#g-rows').append('svg') // .attr("width", bubble.width + bubble.margin.left + bubble.margin.right) // .attr("height", (this.rowHeight * this.data.length) + bubble.margin.top + bubble.margin.bottom) // .append("g") // .attr("transform", "translate(" + bubble.margin.left + "," + bubble.margin.top + ")"); // this.renderRows(); this.renderMonthRows(); this.bindEvents(); this.fixie = new Ink.Fixie('#g-header', {offsetY: 0}) this.navFixie = new Ink.Fixie('#g-barNav', {offsetY: 0}) this.navFixie2 = new Ink.Fixie('#g-barNav-chart', {offsetY: 0}) }, workout_types: [ 'Run', 'Race', 'Long Run', 'Workout' ], renderMonthRows: function(){ var bubble = this.chartStyles.bubbleChart; var me = this; var rows = d3.select('#g-rows').selectAll('.g-row') .data(this.months) .enter() .append('div') .attr('class','g-row') rows.append('h3') .attr('class','g-month-title') .html(function(d,i){ if(d[i]){ return gfx.fullMonths[d[i].formattedDate.getMonth()] } }) .attr('data-interval',function(d){ return d[0].formattedDate.getMonth() }) var bubbleCharts = rows.append('svg') .attr('class','g-bubble-chart') .attr('width', bubble.width + bubble.margin.left + bubble.margin.right) .attr("height", gfx.rowHeight*4.5) .append('g') .attr('transform',"translate(" + bubble.margin.left + "," + bubble.margin.top + ")"); var groups = bubbleCharts.selectAll('.week') .data(function(d){ // // _.select(gfx.data, ) var weeks = _.pluck(d, 'week') return _.filter(gfx.data, function(_d){ return _.contains(weeks,_d.week) }) // return _.toArray(d3.nest().key(function(_d){ return _d.week }).map(d)) }) .enter() .append('g') .attr('class', 'week') .attr('transform',function(d,i){ return 'translate(0,'+(i*gfx.rowHeight)+')'; }); var dividers = groups.selectAll('.divider') .data(function(d){ return [ { distance: d[gfx.scaleBy] }] }) .enter() .append('line') .attr('class', 'divider') .attr('x1', this.chartStyles.bubbleChart.margin.left +10) .attr('y1', me.rowHeight - 1) .attr('x2', 0) .attr('y2', me.rowHeight - 1) // .style('opacity', 0) .transition() .duration(1000) .delay(function(d, i){ return i / me.data.length * 1000 }) .attr('x2',this.bubbleXScale(0)) var totals = groups.selectAll('.weekTotals') .data(function(d){ return [ { interval: d['interval'], distance: d['distance'], time: d['moving_time'], elev_gain: d['elev_gain'] }]}) .enter() .append('g') .attr('class','weekTotals') .attr('transform',function(d,i){ return 'translate(45,91)'; }) .attr('text-anchor','end') totals.append('text') .attr('class', 'weekTotal') .text(function(d){ if(d[gfx.scaleBy]) { return me['format_'+gfx.scaleBy+'_value'](d[gfx.scaleBy])+gfx[gfx.scaleBy+'_suffix']() }}) .attr('text-anchor','end') totals.append('text') .attr('class', 'weekTotal-time') .text(function(d){ if(d[gfx.scaleBy]) { return me['format_time_value'](d.time)+gfx.time_suffix() }}) .attr('transform','translate(0,17)') .attr('text-anchor','end') totals.append('text') .attr('class', 'weekTotal-elev') .text(function(d){ if(d[gfx.scaleBy]) { return me['format_elev_gain_value'](d.elev_gain)+gfx.elev_gain_suffix() }}) .attr('transform','translate(0,32)') .attr('text-anchor','end') var dformat1 = d3.time.format("%b. %e") var dformat2 = d3.time.format("%-e") var months = [ 'Jan.', 'Feb.', 'Mar.', 'Apr.', 'May', 'June', 'July', 'Aug.', 'Sept.', 'Oct.', 'Nov.', 'Dec.' ] var dateRanges = totals.append('text') .attr('class','dateRange') .attr('transform',function(d,i){ if(d[gfx.scaleBy] > 0){ return 'translate(0,-22)'; }else{ return 'translate(0,-2)'; } }) .text(function(d){ _start = new Date(), start = new Date(), _end = new Date(), end = new Date(); year = parseInt(d.interval.substr(0,4),10) week = parseInt(d.interval.substr(5,2),10) _start.setFullYear(year) _start.setMonth(0) _start.setDate(1) _end.setFullYear(year) _end.setMonth(0) _end.setDate((week * 7)) moment_start = moment(_start).add('days',(week*7)) moment_end = moment(_end).add('days',7) if(moment_start.month() === moment_end.month()){ return moment_start.format('MMM. D')+'-'+moment_end.format('D') }else{ return moment_start.format('MMM. D')+'-'+moment_end.format('MMM. D') } }) // .attr('text-anchor','end') var days = groups.selectAll('.dayWrap') .data(function(d){ return d.entry.structured_activities }) .enter() .append('g') .attr('class', function(d){ return 'dayWrap'+' dayWrap-children-'+d.length }) days.each(function(d,n){ if(d.length > 0){ var total = _(d).pluck(gfx.scaleBy).reduce(function(d1,d2){ return d1 + d2}) }else{ var total = 0; } var packSize = [me.runScale(total), me.runScale(total)]; d3.select(this) .attr('transform',function(__d){ return 'translate('+(me.bubbleXScale(n)-(packSize[0]/2))+','+(me.rowHeight - packSize[0] / 2) +')' }) if(total > 0){ pack = d3.layout.pack() .sort(d3.descending) .size(packSize) .value(function(__d){ if(gfx.scaleBy in __d){ return __d[gfx.scaleBy];} else{ return 0 } }) .children( function(__d,i){ return __d; } ) .radius(function(__d){ return me.runScale(__d) }) d3.select(this).selectAll('.run') .data(pack.nodes) .enter() .append('circle') .attr('class', function(d){ var childLength = ('children' in d) ? d.children.length : 0; return 'run run-depth-'+d.depth+' run-children-'+childLength+' run-type-'+d.workout_type }) .attr("transform", function(d) { return "translate(" + d.x + "," + (d.y)+ ")"; }) .attr("r", 0) // .style('opacity', 0) .on('mouseover', function(d,i){ // if (true) {}; if(d.depth === 0){ var parent = this.parentNode; d3.select(parent).classed('active', true) return } var parent = this.parentNode; d3.select(parent).classed('active', true) d3.select(this).classed('active', true) m = d3.mouse(this) // p = d.avg_pace.split(':')[0]+':'+d.avg_pace.split(':')[1] me.tooltip.content('