source: OpenWorkouts-current/ow/static/js/ow.js @ 778d53d

current
Last change on this file since 778d53d was 778d53d, checked in by Borja Lopez <borja@…>, 5 years ago

(#7) Show per-sport stats in the profile page:

  • Show a dropdown list of sports for which the user has activities. By default we choose the sport with most activities.
  • Show a dropdown list of years for which the user has activities. By default we show stats for the current year. If the user picks up a different year, we show the totals (distance, time, elevation, number of workouts) for that year.
  • Show the totals of all time for the chosen sport
  • Property mode set to 100644
File size: 17.4 KB
Line 
1
2/*
3
4  OpenWorkouts Javascript code
5
6*/
7
8
9// Namespace
10var owjs = {};
11
12
13owjs.map = function(spec) {
14
15    "use strict";
16
17    // parameters provided when creating an "instance" of a map
18    var map_id = spec.map_id;
19    var latitude = spec.latitude;
20    var longitude = spec.longitude;
21    var zoom = spec.zoom;
22    var gpx_url = spec.gpx_url;
23    var start_icon = spec.start_icon;
24    var end_icon = spec.end_icon;
25    var shadow = spec.shadow;
26    var elevation = spec.elevation;
27    var zoom_control = spec.zoom_control;
28
29    // OpenStreetMap urls and references
30    var openstreetmap_url = 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png';
31    var openstreetmap_attr = 'Map data &copy; <a href="http://www.osm.org">OpenStreetMap</a>';
32
33    // Some vars reused through the code
34    var map;
35    var gpx;
36    var elevation;
37
38    var create_map = function create_map(latitude, longitude, zoom) {
39        /* Create a Leaflet map, set center point and add tiles */
40        map = L.map(map_id, {zoomControl: zoom_control});
41        map.setView([latitude, longitude], zoom);
42        var tile_layer = L.tileLayer(openstreetmap_url, {
43            attribution: openstreetmap_attr
44        });
45        tile_layer.addTo(map);
46    };
47
48    var add_elevation_chart = function add_elevation_chart() {
49        /*
50           Add the elevation chart support to the map.
51           This has to be called *after* create_map and *before* load_gpx.
52        */
53
54        elevation = L.control.elevation({
55            position: "bottomright",
56            theme: "openworkouts-theme",
57            useHeightIndicator: true, //if false a marker is drawn at map position
58            interpolation: d3.curveLinear,
59            elevationDiv: "#elevation",
60            detachedView: true,
61            responsiveView: true,
62            gpxOptions: {
63                async: true,
64                marker_options: {
65                    startIconUrl: null,
66                    endIconUrl: null,
67                    shadowUrl: null,
68                },
69                polyline_options: {
70                    color: '#EE4056',
71                    opacity: 0.75,
72                    weight: 5,
73                    lineCap: 'round'
74                },
75            },
76        });
77        elevation.loadGPX(map, gpx_url);
78        // var ele_container = elevation.addTo(map);
79    };
80
81    var load_gpx = function load_gpx(gpx_url) {
82        /*
83          Load the gpx from the given url, add it to the map and feed it to the
84          elevation chart
85        */
86        var gpx = new L.GPX(gpx_url, {
87            async: true,
88            marker_options: {
89                startIconUrl: start_icon,
90                endIconUrl: end_icon,
91                shadowUrl: shadow,
92            },
93            polyline_options: {
94                color: '#EE4056',
95                opacity: 0.75,
96                weight: 5,
97                lineCap: 'round'
98            },
99        });
100
101        gpx.on('loaded', function(e) {
102            map.fitBounds(e.target.getBounds());
103        });
104
105        if (elevation) {
106            gpx.on("addline",function(e){
107                elevation.addData(e.line);
108            });
109        };
110
111        gpx.addTo(map);
112    };
113
114    var render = function render() {
115        // create the map, add elevation, load gpx (only if needed, as the
116        // elevation plugin already loads the gpx data)
117        create_map(latitude, longitude, zoom);
118        if (elevation) {
119            add_elevation_chart();
120        }
121        else {
122            load_gpx(gpx_url);
123        }
124    };
125
126    var that = {}
127    that.render = render;
128    return that
129
130};
131
132
133owjs.week_chart = function(spec) {
134
135    "use strict";
136
137    // parameters provided when creating an "instance" of the chart
138    var chart_selector = spec.chart_selector,
139        url = spec.url,
140        current_day_name = spec.current_day_name
141
142    // Helpers
143    function select_x_axis_label(d) {
144        /* Given a value, return the label associated with it */
145        return d3.select('.x-axis')
146            .selectAll('text')
147            .filter(function(x) { return x == d.name; });
148    }
149
150    // Methods
151    var render = function render() {
152        /*
153           Build a d3 bar chart, populated with data from the given url.
154         */
155        var chart = d3.select(chart_selector),
156            margin = {top: 17, right: 0, bottom: 20, left: 0},
157
158            width = +chart.attr("width") - margin.left - margin.right,
159            height = +chart.attr("height") - margin.top - margin.bottom,
160            g = chart.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")"),
161            x = d3.scaleBand().rangeRound([0, width]).padding(0.1),
162            y = d3.scaleLinear().rangeRound([height, 0]);
163
164        d3.json(url, {credentials: "same-origin"}).then(function (data) {
165            x.domain(data.map(function (d) {
166                return d.name;
167            }));
168
169            y.domain([0, d3.max(data, function (d) {
170                return Number(d.distance);
171            })]);
172
173            g.append("g")
174                .attr('class', 'x-axis')
175                .attr("transform", "translate(0," + height + ")")
176                .call(d3.axisBottom(x))
177
178            g.selectAll(".bar")
179                .data(data)
180                .enter().append("rect")
181                .attr("class", function(d) {
182                    if (d.name == current_day_name){
183                        select_x_axis_label(d).attr('style', "font-weight: bold;");
184                        return 'bar current'
185                    }
186                    else {
187                        return 'bar'
188                    }
189                })
190                .attr("x", function (d) {
191                    return x(d.name);
192                })
193                .attr("y", function (d) {
194                    return y(Number(d.distance));
195                })
196                .attr("width", x.bandwidth())
197                .attr("height", function (d) {
198                    return height - y(Number(d.distance));
199                })
200                .on('mouseover', function(d) {
201                    if (d.name != current_day_name){
202                        select_x_axis_label(d).attr('style', "font-weight: bold;");
203                    }
204                })
205                .on('mouseout', function(d) {
206                    if (d.name != current_day_name){
207                        select_x_axis_label(d).attr('style', "font-weight: regular;");
208                    }
209                });
210
211            g.selectAll(".text")
212                .data(data)
213                .enter()
214                .append("text")
215                .attr("class","label")
216                .attr("x", function (d) {
217                    return x(d.name) + x.bandwidth()/2;
218                })
219                .attr("y", function (d) {
220                    /*
221                      Get the value for the current bar, then get the maximum
222                      value to be displayed in the bar, which is used to
223                      calculate the proper position of the label for this bar,
224                      relatively to its height (1% above the bar)
225                     */
226                    var max = y.domain()[1];
227                    return y(d.distance + y.domain()[1] * 0.02);
228            })
229                .text(function(d) {
230                    if (Number(d.distance) > 0) {
231                        return d.distance;
232                    }
233                });
234
235        });
236    };
237
238    var that = {}
239    that.render = render;
240    return that
241
242};
243
244
245owjs.year_chart = function(spec) {
246
247    "use strict";
248
249    // parameters provided when creating an "instance" of the chart
250    var chart_selector = spec.chart_selector,
251        filters_selector = spec.filters_selector,
252        switcher_selector = spec.switcher_selector,
253        is_active_class = spec.is_active_class,
254        is_active_selector = '.' + is_active_class,
255        urls = spec.urls,
256        current_month = spec.current_month,
257        current_week = spec.current_week,
258        y_axis_labels = spec.y_axis_labels,
259        filter_by = spec.filter_by,
260        url = spec.url;
261
262    // Helpers
263    function select_x_axis_label(d) {
264        /* Given a value, return the label associated with it */
265        return d3.select('.x-axis-b')
266            .selectAll('text')
267            .filter(function(x) { return x == d.name; });
268    };
269
270    function get_y_value(d, filter_by) {
271        return Number(d[filter_by]);
272    };
273
274    function get_y_axis_label(filter_by) {
275        return y_axis_labels[filter_by];
276    };
277
278    function get_name_for_x(d) {
279        if (d.week == undefined || d.week == 0) {
280            // Monthly chart or first week of the weekly chart, return
281            // the name of the month, which will be shown in the x-axis
282            // ticks
283            return d.name;
284        }
285        else {
286            // Weekly chart, week other than the first, return the id so
287            // we can place it in the chart, we won't show this to the
288            // user
289            return d.id
290        }
291    }
292
293    // Methods
294    var filters_setup = function filters_setup() {
295        $(filters_selector).on('click', function(e) {
296            e.preventDefault();
297            $(filters_selector + is_active_selector).removeClass(is_active_class);
298            /* $(this).removeClass('is-active'); */
299            filter_by = $(this).attr('class').split('-')[1]
300            $(this).addClass(is_active_class);
301            var chart = d3.select(chart_selector);
302            chart.selectAll("*").remove();
303            render(filter_by, url);
304
305        });
306    };
307
308    var switcher_setup = function switcher_setup() {
309        $(switcher_selector).on('click', function(e) {
310            e.preventDefault();
311            $(switcher_selector + is_active_selector).removeClass(is_active_class);
312            url = $(this).attr('class').split('-')[1]
313            $(this).addClass(is_active_class);
314            var chart = d3.select(chart_selector);
315            chart.selectAll("*").remove();
316            render(filter_by, url);
317        });
318    };
319
320    var render = function render(filter_by, url) {
321        /*
322          Build a d3 bar chart, populated with data from the given url.
323        */
324        var chart = d3.select(chart_selector),
325            margin = {top: 20, right: 20, bottom: 30, left: 50},
326            width = +chart.attr("width") - margin.left - margin.right,
327            height = +chart.attr("height") - margin.top - margin.bottom,
328            g = chart.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")"),
329            x = d3.scaleBand().rangeRound([0, width]).padding(0.1),
330            y = d3.scaleLinear().rangeRound([height, 0]);
331
332        d3.json(urls[url], {credentials: "same-origin"}).then(function (data) {
333            x.domain(data.map(function (d) {
334                return get_name_for_x(d);
335                // return d.name;
336            }));
337
338            y.domain([0, d3.max(data, function (d) {
339                return get_y_value(d, filter_by);
340            })]);
341
342            g.append("g")
343                .attr('class', 'x-axis-b')
344                .attr("transform", "translate(0," + height + ")")
345                .call(d3.axisBottom(x))
346
347            g.append("g")
348                .call(d3.axisLeft(y))
349                .append("text")
350                .attr("fill", "#000")
351                .attr("transform", "rotate(-90)")
352                .attr("y", 6)
353                .attr("dy", "0.71em")
354                .attr("text-anchor", "end")
355                .text(get_y_axis_label(filter_by));
356
357            g.selectAll(".bar")
358                .data(data)
359                .enter().append("rect")
360                .attr("class", function(d) {
361                    var sel_week = current_month + '-' + current_week;
362                    if (d.id == current_month || d.id == sel_week){
363                        /* Bar for the currently selected month or week */
364                        select_x_axis_label(d).attr('style', "font-weight: bold;");
365                        return 'bar current';
366                    }
367                    else {
368                        if (!current_week && d.id.indexOf(current_month) >=0 ) {
369                            /*
370                               User selected a month, then switched to weekly
371                               view, we do highlight all the bars for weeks in
372                               that month
373                            */
374                            select_x_axis_label(d).attr('style', "font-weight: bold;");
375                            return 'bar current';
376                        }
377                        else {
378                            /* Non-selected bar */
379                            return 'bar';
380                        }
381
382                    }
383                })
384                .attr("x", function (d) {
385                    return x(get_name_for_x(d));
386                })
387                .attr("y", function (d) {
388                    return y(get_y_value(d, filter_by));
389                })
390                .attr("width", x.bandwidth())
391                .attr("height", function (d) {
392                    return height - y(get_y_value(d, filter_by));
393                })
394                .on('mouseover', function(d) {
395                    if (d.id != current_month){
396                        select_x_axis_label(d).attr('style', "font-weight: bold;");
397                    }
398                })
399                .on('mouseout', function(d) {
400                    if (d.id != current_month){
401                        select_x_axis_label(d).attr('style', "font-weight: regular;");
402                    }
403                })
404                .on('click', function(d) {
405                    window.location.href = d.url;
406                });
407
408            if (url == 'monthly') {
409                g.selectAll(".text")
410                    .data(data)
411                    .enter()
412                    .append("text")
413                    .attr("class","label")
414                    .attr("x", function (d) {
415                        return x(get_name_for_x(d)) + x.bandwidth()/2;
416                    })
417                    .attr("y", function (d) {
418                        /*
419                          Get the value for the current bar, then get the maximum
420                          value to be displayed in the bar, which is used to
421                          calculate the proper position of the label for this bar,
422                          relatively to its height (1% above the bar)
423                        */
424                        var value = get_y_value(d, filter_by);
425                        var max = y.domain()[1];
426                        return y(value + y.domain()[1] * 0.01);
427                    })
428                    .text(function(d) {
429                        var value = get_y_value(d, filter_by)
430                        if ( value > 0) {
431                            return value;
432                        }
433                    });
434            }
435
436            if (url == 'weekly') {
437                g.selectAll(".tick")
438                    .each(function (d, i) {
439                        /*
440                          Remove from the x-axis tickets those without letters
441                          on them (useful for the weekly chart)
442                        */
443                        if (d !== parseInt(d, 10)) {
444                            if(!d.match(/[a-z]/i)) {
445                                this.remove();
446                            }
447                        }
448                    });
449            }
450        });
451    };
452
453    var that = {}
454    that.filters_setup = filters_setup;
455    that.switcher_setup = switcher_setup;
456    that.render = render;
457    return that
458
459};
460
461
462owjs.map_shots = function(spec) {
463
464    "use strict";
465
466    var img_selector = spec.img_selector;
467
468    var run = function run(){
469        $(img_selector).each(function(){
470            var img = $(this);
471            var a = $(this).parent();
472            var url = a.attr('href') + 'map-shot';
473            var jqxhr = $.getJSON(url, function(info) {
474                img.fadeOut('fast', function () {
475                    img.attr('src', info['url']);
476                    img.fadeIn('fast');
477                });
478                img.removeClass('js-needs-map');
479            });
480        });
481    };
482
483    var that = {}
484    that.run = run;
485    return that
486
487};
488
489
490owjs.sport_stats = function(spec) {
491
492    "use strict";
493
494    var link_selector = spec.link_selector;
495    var stats_selector = spec.stats_selector;
496    var selected = spec.selected;
497    var dropdown_selector = spec.dropdown_selector;
498    var current_year = spec.current_year;
499    var year_link_selector = spec.year_link_selector;
500
501    var setup = function setup() {
502        // Hide all sports stats by default
503        $(stats_selector).hide();
504        // Show the pre-selected one
505        $(selected).show();
506
507        $(link_selector).on('click', function(e) {
508            e.preventDefault();
509            var selected = $(this).attr('class').split(' ')[1];
510            var sport = selected.split('-')[1]
511            // Hide them all
512            $(stats_selector).hide();
513            // Show the selected one
514            $(stats_selector + '.' + selected).show();
515            // Update the sport on the sports selector widget
516            $(dropdown_selector + ' strong').html(sport);
517            // finally "click" on the proper year to be displayed for this sport
518            $(year_link_selector + sport + '-' + current_year).click();
519        });
520    };
521
522    var that = {}
523    that.setup = setup;
524    return that
525
526};
527
528
529owjs.year_stats = function(spec) {
530
531    "use strict";
532
533    var link_selector = spec.link_selector;
534    var stats_selector = spec.stats_selector;
535    var selected = spec.selected;
536    var dropdown_selector = spec.dropdown_selector;
537
538    var setup = function setup() {
539        // Hide all years stats by default
540        $(stats_selector).hide();
541        // Show the pre-selected one
542        $(selected).show();
543
544        $(link_selector).on('click', function(e) {
545            e.preventDefault();
546            var selected = $(this).attr('class').split(' ')[1];
547            var sport = selected.split('-')[1]
548            var year = selected.split('-')[2]
549            // Hide them all
550            $(stats_selector).hide();
551            // Show the selected one
552            $(stats_selector + '.' + selected).show();
553            // Update the year on the years selector widget
554            $(dropdown_selector + sport + ' strong').html(year);
555        });
556    };
557
558    var that = {}
559    that.setup = setup;
560    return that
561
562};
Note: See TracBrowser for help on using the repository browser.