Changeset 5bdfbfb in OpenWorkouts-current


Ignore:
Timestamp:
Jan 25, 2019, 12:42:33 AM (5 years ago)
Author:
borja <borja@…>
Branches:
current, feature/docs, master
Children:
26220ba, 7783f97
Parents:
421f05f
Message:

(#7) Show year/month/weekly stats in the dashboard for the user,

including a bar chart for activity during the current week

Location:
ow
Files:
6 edited

Legend:

Unmodified
Added
Removed
  • ow/static/css/openworkouts.css

    r421f05f r5bdfbfb  
    1212    background-color: #eeeeee;
    1313}
     14
     15/* dashboard, weekly stats */
     16
     17span.week_totals_left {
     18    padding-right: 5px;
     19    border-right: 1px solid #e1e1e1;
     20}
     21
     22span.week_totals_right {
     23    padding-left: 5px;
     24}
     25
     26.x-axis path, .x-axis line {
     27    fill: none;
     28    stroke: none;
     29}
     30
     31.bar {
     32    fill: #f8b5be;
     33}
     34
     35.bar:hover {
     36    fill: #ee4056;
     37}
     38
     39.current {
     40    fill: #ee4056;
     41}
     42
     43text.label {
     44    font-size: 0.6em;
     45    text-anchor: middle;
     46}
  • ow/static/js/ow.js

    r421f05f r5bdfbfb  
    123123
    124124};
     125
     126
     127owjs.week_chart = function(spec) {
     128
     129    "use strict";
     130
     131    // parameters provided when creating an "instance" of the chart
     132    var chart_selector = spec.chart_selector,
     133        url = spec.url,
     134        current_day_name = spec.current_day_name
     135
     136    // Helpers
     137    function select_x_axis_label(d) {
     138        /* Given a value, return the label associated with it */
     139        return d3.select('.x-axis')
     140            .selectAll('text')
     141            .filter(function(x) { return x == d.name; });
     142    }
     143
     144    // Methods
     145    var render = function render() {
     146        /*
     147           Build a d3 bar chart, populated with data from the given url.
     148         */
     149        var chart = d3.select("svg"),
     150            margin = {top: 20, right: 20, bottom: 30, left: 50},
     151            width = +chart.attr("width") - margin.left - margin.right,
     152            height = +chart.attr("height") - margin.top - margin.bottom,
     153            g = chart.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")"),
     154            x = d3.scaleBand().rangeRound([0, width]).padding(0.1),
     155            y = d3.scaleLinear().rangeRound([height, 0]);
     156
     157        d3.json(url).then(function (data) {
     158            x.domain(data.map(function (d) {
     159                return d.name;
     160            }));
     161
     162            y.domain([0, d3.max(data, function (d) {
     163                return Number(d.distance);
     164            })]);
     165
     166            g.append("g")
     167                .attr('class', 'x-axis')
     168                .attr("transform", "translate(0," + height + ")")
     169                .call(d3.axisBottom(x))
     170
     171            g.selectAll(".bar")
     172                .data(data)
     173                .enter().append("rect")
     174                .attr("class", function(d) {
     175                    if (d.name == current_day_name){
     176                        select_x_axis_label(d).attr('style', "font-weight: bold;");
     177                        return 'bar current'
     178                    }
     179                    else {
     180                        return 'bar'
     181                    }
     182                })
     183                .attr("x", function (d) {
     184                    return x(d.name);
     185                })
     186                .attr("y", function (d) {
     187                    return y(Number(d.distance));
     188                })
     189                .attr("width", x.bandwidth())
     190                .attr("height", function (d) {
     191                    return height - y(Number(d.distance));
     192                })
     193                .on('mouseover', function(d) {
     194                    if (d.name != current_day_name){
     195                        select_x_axis_label(d).attr('style', "font-weight: bold;");
     196                    }
     197                })
     198                .on('mouseout', function(d) {
     199                    if (d.name != current_day_name){
     200                        select_x_axis_label(d).attr('style', "font-weight: regular;");
     201                    }
     202                });
     203
     204            g.selectAll(".text")
     205                .data(data)
     206                .enter()
     207                .append("text")
     208                .attr("class","label")
     209                .attr("x", function (d) {
     210                    return x(d.name) + x.bandwidth()/2;
     211                })
     212                .attr("y", function (d) {
     213                    return y(Number(d.distance) + 5);
     214                })
     215                .text(function(d) {
     216                    if (Number(d.distance) > 0) {
     217                        return d.distance;
     218                    }
     219                });
     220
     221        });
     222    };
     223
     224    var that = {}
     225    that.render = render;
     226    return that
     227
     228};
  • ow/templates/dashboard.pt

    r421f05f r5bdfbfb  
    142142              </ul>
    143143          </tal:activity_tree>
     144
     145          <tal:stats>
     146
     147            <div class="week-stats js-week-stats">
     148              <h3><tal:t i18n:translate="">This week</tal:t></h3>
     149              <h4 tal:define="totals context.week_totals">
     150                <span class="week_totals_left">
     151                  <tal:d tal:content="round(totals['distance'])"></tal:d>
     152                  <tal:t i18n:translate="">kms</tal:t>
     153                </span>
     154                <span class="week_totals_right">
     155                  <tal:hms tal:define="hms timedelta_to_hms(totals['time'])">
     156                    <tal:h tal:content="str(hms[0]).zfill(2)"></tal:h>
     157                    <tal:t i18n:translate="">hours</tal:t>,
     158                    <tal:h tal:content="str(hms[1]).zfill(2)"></tal:h>
     159                    <tal:t i18n:translate="">min.</tal:t>
     160                  </tal:hms>
     161                </span>
     162              </h4>
     163              <svg width="300" height="200"></svg>
     164              <style>
     165
     166              </style>
     167            </div>
     168
     169            <div class="user-stats">
     170              <tal:year-stats tal:repeat="year context.activity_years">
     171                <h3><a href="" tal:content="year"></a></h3>
     172                <ul tal:define="stats context.stats(year)">
     173                  <li>
     174                    <span i18n:translate="">Workouts:</span>
     175                    <span tal:content="stats['workouts']"></span>
     176                  </li>
     177                  <li>
     178                    <span i18n:translate="">Distance:</span>
     179                    <span tal:content="round(stats['distance'])"></span> kms
     180                  </li>
     181                  <li>
     182                    <span i18n:translate="">Time:</span>
     183                    <tal:hms tal:define="hms timedelta_to_hms(stats['time'])">
     184                      <span>
     185                        <tal:h tal:content="str(hms[0]).zfill(2)"></tal:h>
     186                        <tal:t i18n:translate="">hours</tal:t>,
     187                        <tal:h tal:content="str(hms[1]).zfill(2)"></tal:h>
     188                        <tal:t i18n:translate="">min.</tal:t>
     189                      </span>
     190                    </tal:hms>
     191                  </li>
     192                  <li>
     193                    <span i18n:translate="">Elevation:</span>
     194                    <span tal:content="stats['elevation']"></span> m
     195                  </li>
     196                </ul>
     197              </tal:year-stats>
     198            </div>
     199          </tal:stats>
     200
    144201      </aside>
    145202
     
    148205  </metal:content>
    149206
     207  <metal:body-js metal:fill-slot="body-js">
     208    <script src="${request.static_url('ow:static/components/d3/d3.min.js')}"></script>
     209    <script src="${request.static_url('ow:static/js/ow.js')}"></script>
     210
     211    <script type="text/javascript">
     212     var week_chart = owjs.week_chart({
     213         chart_selector: 'div.js-week-stats',
     214         url: "${request.resource_url(context, 'week')}",
     215         current_day_name: "${current_day_name}"
     216     });
     217     week_chart.render();
     218    </script>
     219
     220  </metal:body-js>
     221
    150222</html>
  • ow/tests/views/test_user.py

    r421f05f r5bdfbfb  
    153153        request = dummy_request
    154154        response = user_views.dashboard(john, request)
    155         assert len(response) == 4
    156         assert 'month_name' in response.keys()
     155        assert len(response) == 6
     156        assert 'month_name' in response.keys()
     157        assert response['current_year'] == datetime.now().year
     158        assert response['current_day_name'] == datetime.now().strftime('%a')
    157159        # this user has a single workout, in 2015
    158160        assert response['viewing_year'] == 2015
     
    168170        request.GET['year'] = 2015
    169171        response = user_views.dashboard(john, request)
    170         assert len(response) == 4
    171         assert 'month_name' in response.keys()
     172        assert len(response) == 6
     173        assert 'month_name' in response.keys()
     174        assert response['current_year'] == datetime.now().year
     175        assert response['current_day_name'] == datetime.now().strftime('%a')
    172176        # this user has a single workout, in 2015
    173177        assert response['viewing_year'] == 2015
     
    177181        request.GET['year'] = 2000
    178182        response = user_views.dashboard(john, request)
    179         assert len(response) == 4
    180         assert 'month_name' in response.keys()
     183        assert len(response) == 6
     184        assert 'month_name' in response.keys()
     185        assert response['current_year'] == datetime.now().year
     186        assert response['current_day_name'] == datetime.now().strftime('%a')
    181187        # this user has a single workout, in 2015
    182188        assert response['viewing_year'] == 2000
     
    195201        request.GET['month'] = 6
    196202        response = user_views.dashboard(john, request)
    197         assert len(response) == 4
    198         assert 'month_name' in response.keys()
     203        assert len(response) == 6
     204        assert 'month_name' in response.keys()
     205        assert response['current_year'] == datetime.now().year
     206        assert response['current_day_name'] == datetime.now().strftime('%a')
    199207        # this user has a single workout, in 2015
    200208        assert response['viewing_year'] == 2015
     
    204212        request.GET['month'] = 2
    205213        response = user_views.dashboard(john, request)
    206         assert len(response) == 4
    207         assert 'month_name' in response.keys()
     214        assert len(response) == 6
     215        assert 'month_name' in response.keys()
     216        assert response['current_year'] == datetime.now().year
     217        assert response['current_day_name'] == datetime.now().strftime('%a')
    208218        # this user has a single workout, in 2015
    209219        assert response['viewing_year'] == 2015
     
    214224        request.GET['month'] = 6
    215225        response = user_views.dashboard(john, request)
    216         assert len(response) == 4
    217         assert 'month_name' in response.keys()
     226        assert len(response) == 6
     227        assert 'month_name' in response.keys()
     228        assert response['current_year'] == datetime.now().year
     229        assert response['current_day_name'] == datetime.now().strftime('%a')
    218230        # this user has a single workout, in 2015
    219231        assert response['viewing_year'] == 2010
     
    230242        request.GET['month'] = 5
    231243        response = user_views.dashboard(john, request)
    232         assert len(response) == 4
    233         assert 'month_name' in response.keys()
     244        assert len(response) == 6
     245        assert 'month_name' in response.keys()
     246        assert response['current_year'] == datetime.now().year
     247        assert response['current_day_name'] == datetime.now().strftime('%a')
    234248        # this user has a single workout, in 2015
    235249        assert response['viewing_year'] == 2015
     
    239253        request.GET['month'] = 6
    240254        response = user_views.dashboard(john, request)
    241         assert len(response) == 4
    242         assert 'month_name' in response.keys()
     255        assert len(response) == 6
     256        assert 'month_name' in response.keys()
     257        assert response['current_year'] == datetime.now().year
     258        assert response['current_day_name'] == datetime.now().strftime('%a')
    243259        # this user has a single workout, in 2015
    244260        assert response['viewing_year'] == 2015
  • ow/views/user.py

    r421f05f r5bdfbfb  
    146146
    147147    return {
     148        'current_year': datetime.now().year,
     149        'current_day_name': datetime.now().strftime('%a'),
    148150        'month_name': month_name,
    149151        'viewing_year': viewing_year,
  • ow/views/workout.py

    r421f05f r5bdfbfb  
     1from decimal import Decimal
    12from datetime import datetime, timedelta, time, timezone
    23
     
    121122                                 tzinfo=timezone.utc)
    122123        context.start = start
     124        # ensure distance is a decimal
     125        context.distance = Decimal(context.distance)
    123126        catalog = get_catalog(context)
    124127        reindex_object(catalog, context)
Note: See TracChangeset for help on using the changeset viewer.