├── settings ├── __init__.py ├── production.py ├── development.py └── base.py ├── project ├── apps │ ├── chinup │ │ ├── __init__.py │ │ ├── migrations │ │ │ ├── __init__.py │ │ │ ├── 0003_auto__del_field_metricrecord_measurement.py │ │ │ ├── 0005_auto__chg_field_metricrecord_datetime.py │ │ │ ├── 0002_auto__add_field_metricrecord_metric.py │ │ │ ├── 0006_auto__chg_field_metricrecord_notes.py │ │ │ ├── 0004_auto__add_field_metricrecord_measurement.py │ │ │ ├── 0007_auto__add_field_metric_boolean.py │ │ │ ├── 0008_auto__chg_field_metric_description_worst__chg_field_metric_description.py │ │ │ └── 0001_initial.py │ │ ├── admin.py │ │ ├── urls.py │ │ ├── models.py │ │ ├── templates │ │ │ └── chinup │ │ │ │ ├── home.html │ │ │ │ └── input.html │ │ ├── static │ │ │ └── js │ │ │ │ └── chinup.js │ │ └── views.py │ ├── correlations │ │ ├── __init__.py │ │ ├── models.py │ │ ├── templatetags │ │ │ ├── __init__.py │ │ │ └── correlation_tags.py │ │ ├── static │ │ │ └── js │ │ │ │ └── correlations.js │ │ ├── urls.py │ │ ├── views.py │ │ └── templates │ │ │ └── correlations │ │ │ └── correlations.html │ └── stats │ │ ├── templatetags │ │ ├── __init__.py │ │ └── dictionary.py │ │ ├── __init__.py │ │ ├── models.py │ │ ├── urls.py │ │ ├── static │ │ └── js │ │ │ └── stats.js │ │ ├── views.py │ │ └── templates │ │ └── stats │ │ └── stats.html ├── static │ ├── css │ │ ├── correlations.css │ │ ├── correlations.less │ │ ├── graphs.css │ │ ├── graphs.less │ │ ├── home.css │ │ ├── home.less │ │ ├── input.css │ │ ├── input.less │ │ ├── main.css │ │ └── main.less │ ├── fonts │ │ ├── glyphicons-halflings-regular.eot │ │ ├── glyphicons-halflings-regular.ttf │ │ ├── glyphicons-halflings-regular.woff │ │ └── glyphicons-halflings-regular.svg │ └── js │ │ ├── jquery.autosize.min.js │ │ ├── bootstrap.min.js │ │ └── gauge.min.js └── templates │ └── base.html ├── Procfile ├── docs └── img │ └── screenshots │ ├── input.png │ ├── stats.png │ ├── home_view.png │ ├── correlations.png │ └── pin_passcode.png ├── wsgi.py ├── manage.py ├── requirements.txt ├── .gitignore ├── generate_data.py ├── polished_backend.py ├── LICENSE.txt └── README.md /settings/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /project/apps/chinup/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /project/apps/correlations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /project/apps/correlations/models.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /project/apps/chinup/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /project/apps/stats/templatetags/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /project/apps/correlations/templatetags/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /project/apps/stats/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = 'Eric' 2 | -------------------------------------------------------------------------------- /project/apps/stats/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | 4 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: gunicorn -b 0.0.0.0:$PORT --log-level debug wsgi:application 2 | -------------------------------------------------------------------------------- /project/static/css/correlations.css: -------------------------------------------------------------------------------- 1 | .correlation_selector { 2 | margin-bottom: 30px; 3 | } 4 | -------------------------------------------------------------------------------- /project/static/css/correlations.less: -------------------------------------------------------------------------------- 1 | .correlation_selector { 2 | margin-bottom: 30px; 3 | } 4 | -------------------------------------------------------------------------------- /docs/img/screenshots/input.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ckcollab/chin-up/HEAD/docs/img/screenshots/input.png -------------------------------------------------------------------------------- /docs/img/screenshots/stats.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ckcollab/chin-up/HEAD/docs/img/screenshots/stats.png -------------------------------------------------------------------------------- /docs/img/screenshots/home_view.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ckcollab/chin-up/HEAD/docs/img/screenshots/home_view.png -------------------------------------------------------------------------------- /docs/img/screenshots/correlations.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ckcollab/chin-up/HEAD/docs/img/screenshots/correlations.png -------------------------------------------------------------------------------- /docs/img/screenshots/pin_passcode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ckcollab/chin-up/HEAD/docs/img/screenshots/pin_passcode.png -------------------------------------------------------------------------------- /project/static/css/graphs.css: -------------------------------------------------------------------------------- 1 | .graph-container { 2 | text-align: center; 3 | } 4 | .btn-group { 5 | padding-bottom: 40px; 6 | } 7 | -------------------------------------------------------------------------------- /project/static/css/graphs.less: -------------------------------------------------------------------------------- 1 | .graph-container { 2 | text-align: center; 3 | } 4 | 5 | .btn-group { 6 | padding-bottom: 40px; 7 | } 8 | -------------------------------------------------------------------------------- /project/static/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ckcollab/chin-up/HEAD/project/static/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /project/static/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ckcollab/chin-up/HEAD/project/static/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /project/static/css/home.css: -------------------------------------------------------------------------------- 1 | .notes .note { 2 | font-weight: bold; 3 | } 4 | .notes .note small { 5 | font-weight: normal; 6 | margin-left: 5px; 7 | } 8 | -------------------------------------------------------------------------------- /project/static/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ckcollab/chin-up/HEAD/project/static/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /project/apps/chinup/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from .models import Metric, MetricRecord 4 | 5 | 6 | admin.site.register(Metric) 7 | admin.site.register(MetricRecord) 8 | -------------------------------------------------------------------------------- /project/static/css/home.less: -------------------------------------------------------------------------------- 1 | .notes { 2 | .note { 3 | font-weight: bold; 4 | 5 | small { 6 | font-weight: normal; 7 | margin-left: 5px; 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /project/apps/stats/templatetags/dictionary.py: -------------------------------------------------------------------------------- 1 | from django import template 2 | 3 | 4 | register = template.Library() 5 | 6 | 7 | @register.filter 8 | def get_item(dictionary, key): 9 | return dictionary.get(key) 10 | -------------------------------------------------------------------------------- /wsgi.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings.production") 4 | 5 | from django.core.wsgi import get_wsgi_application 6 | from dj_static import Cling 7 | 8 | application = Cling(get_wsgi_application()) 9 | -------------------------------------------------------------------------------- /project/apps/correlations/static/js/correlations.js: -------------------------------------------------------------------------------- 1 | function CorrelationsController($scope, $http, $location) { 2 | $scope.resize_graph = function() { 3 | setTimeout(function() { 4 | $(window).resize(); 5 | }, 1); 6 | }; 7 | } 8 | -------------------------------------------------------------------------------- /project/apps/stats/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.conf.urls import patterns, include, url 3 | from django.contrib import admin 4 | 5 | 6 | urlpatterns = patterns( 7 | '', 8 | url(r'stats[/]$', 'stats.views.stats_view', name='stats'), 9 | ) 10 | -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | 6 | if __name__ == "__main__": 7 | os.environ["DJANGO_SETTINGS_MODULE"] = "settings.development" 8 | 9 | from django.core.management import execute_from_command_line 10 | 11 | execute_from_command_line(sys.argv) 12 | -------------------------------------------------------------------------------- /project/apps/correlations/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.conf.urls import patterns, include, url 3 | from django.contrib import admin 4 | 5 | 6 | urlpatterns = patterns( 7 | '', 8 | url(r'correlations[/]$', 'correlations.views.correlation_view', name='correlations'), 9 | ) 10 | -------------------------------------------------------------------------------- /project/static/css/input.css: -------------------------------------------------------------------------------- 1 | .metric_input { 2 | position: relative; 3 | margin: 20px 0; 4 | } 5 | .metric_input .min_label, 6 | .metric_input .max_label { 7 | position: absolute; 8 | top: 0; 9 | } 10 | .metric_input input { 11 | margin: 0 20px; 12 | width: 65%; 13 | } 14 | textarea { 15 | border-color: #e7e7e7; 16 | resize: vertical; 17 | } 18 | -------------------------------------------------------------------------------- /project/apps/correlations/templatetags/correlation_tags.py: -------------------------------------------------------------------------------- 1 | import math 2 | 3 | from django import template 4 | 5 | 6 | register = template.Library() 7 | 8 | 9 | @register.filter 10 | def is_numeric(value): 11 | try: 12 | float(value) 13 | 14 | if math.isnan(value): 15 | return False 16 | 17 | return True 18 | except ValueError: 19 | return False 20 | -------------------------------------------------------------------------------- /project/static/css/input.less: -------------------------------------------------------------------------------- 1 | @import "lesshat-prefixed.less"; 2 | 3 | .metric_input { 4 | position: relative; 5 | margin: 20px 0; 6 | 7 | .min_label, .max_label { 8 | position: absolute; 9 | top: 0; 10 | } 11 | 12 | input { 13 | margin: 0 20px; 14 | width: 65%; 15 | } 16 | } 17 | 18 | textarea { 19 | border-color: #e7e7e7; 20 | resize: vertical; 21 | } 22 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Django==1.6.1 2 | South==0.8.4 3 | dj-static==0.0.5 4 | django-debug-toolbar==1.0 5 | django-extensions==1.3.3 6 | django-pin-passcode>=0.1.3 7 | gunicorn==18.0 8 | ipdb==0.8 9 | ipython==1.1.0 10 | psycopg2==2.5.1 11 | pyreadline==2.0 12 | python-dateutil==2.2 13 | raven==4.2.1 14 | six==1.5.2 15 | sqlparse==0.1.10 16 | static==0.4 17 | wsgiref==0.1.2 18 | django-qsstats-magic 19 | numpy==1.8.1 20 | bugsnag==2.2.0 21 | django-grappelli==2.6.3 22 | -------------------------------------------------------------------------------- /settings/production.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import os 3 | 4 | from .base import * 5 | 6 | 7 | # logger = logging.getLogger('waitress') 8 | # logger.setLevel(logging.DEBUG) 9 | 10 | 11 | logging.basicConfig( 12 | stream=sys.stdout, 13 | level=logging.DEBUG, 14 | format='"%(asctime)s %(levelname)8s %(name)s - %(message)s"', 15 | datefmt='%H:%M:%S' 16 | ) 17 | 18 | SECRET_KEY = os.environ.get("DJANGO_SECRET_KEY", None) 19 | if SECRET_KEY is None: 20 | assert ValueError("Missing DJANGO_SECRET_KEY environment variable!") 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/* 2 | venv/* 3 | polished 4 | polished_output/ 5 | 6 | *.py[cod] 7 | 8 | # C extensions 9 | *.so 10 | 11 | # Packages 12 | *.egg 13 | *.egg-info 14 | dist 15 | build 16 | eggs 17 | parts 18 | bin 19 | var 20 | sdist 21 | develop-eggs 22 | .installed.cfg 23 | lib 24 | lib64 25 | __pycache__ 26 | 27 | # Installer logs 28 | pip-log.txt 29 | 30 | # Unit test / coverage reports 31 | .coverage 32 | .tox 33 | nosetests.xml 34 | 35 | # Translations 36 | *.mo 37 | 38 | # Mr Developer 39 | .mr.developer.cfg 40 | .project 41 | .pydevproject 42 | -------------------------------------------------------------------------------- /settings/development.py: -------------------------------------------------------------------------------- 1 | from .base import * 2 | 3 | DEBUG = True 4 | TEMPLATE_DEBUG = DEBUG 5 | 6 | INSTALLED_APPS += ( 7 | 'debug_toolbar', 8 | ) 9 | 10 | MIDDLEWARE_CLASSES += ( 11 | 'debug_toolbar.middleware.DebugToolbarMiddleware', 12 | ) 13 | 14 | DEBUG_TOOLBAR_CONFIG = { 15 | 'INTERCEPT_REDIRECTS': False, 16 | 'HIDE_DJANGO_SQL': False, 17 | 'TAG': 'div', 18 | 'ENABLE_STACKTRACES' : True, 19 | } 20 | 21 | if 'test' in sys.argv: 22 | DATABASES['default'] = {'ENGINE': 'django.db.backends.sqlite3'} 23 | 24 | SOUTH_TESTS_MIGRATE = False 25 | -------------------------------------------------------------------------------- /generate_data.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | 3 | from chinup.models import Metric, MetricRecord 4 | 5 | 6 | daily_metrics = ['Strength', 'Happiness', 'Flexibility', 'Relationships', 'Motiviation', 'Endurance'] 7 | 8 | for metric_name in daily_metrics: 9 | metric = Metric.objects.create(name=metric_name, daily=True) 10 | MetricRecord.objects.create(metric=metric, measurement=7, datetime=datetime.datetime.now()) 11 | 12 | monthly_metrics = ['Life goals', 'Power', 'Nature'] 13 | 14 | for metric_name in monthly_metrics: 15 | metric = Metric.objects.create(name=metric_name, daily=False, monthly=True) 16 | MetricRecord.objects.create(metric=metric, measurement=7, datetime=datetime.datetime.now()) 17 | 18 | -------------------------------------------------------------------------------- /project/apps/stats/static/js/stats.js: -------------------------------------------------------------------------------- 1 | function StatsController($scope, $http, $location) { 2 | $scope.select_graph = function(graph_name) { 3 | localStorage.setItem("last_graph_name", graph_name); 4 | 5 | $scope.graph_name = graph_name; 6 | setTimeout(function() { 7 | $(window).resize(); 8 | }, 1); 9 | 10 | }; 11 | 12 | $scope.is_graph_showing = function(graph_name) { 13 | return $scope.graph_name === graph_name; 14 | }; 15 | 16 | // Init 17 | $scope.init_graphs = function() { 18 | var graph_name = localStorage.getItem("last_graph_name") || "month-to-month"; 19 | 20 | $scope.select_graph(graph_name); 21 | }; 22 | } 23 | -------------------------------------------------------------------------------- /project/apps/chinup/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.conf.urls import patterns, include, url 3 | from django.contrib import admin 4 | 5 | 6 | admin.autodiscover() 7 | 8 | urlpatterns = patterns('', 9 | url(r'^grappelli/', include('grappelli.urls')), 10 | url(r'^admin/', include(admin.site.urls)), 11 | url('auth', include('django.contrib.auth.urls', namespace='auth')), 12 | 13 | url(r'^$', 'chinup.views.home', name='home'), 14 | url(r'^input[/]$', 'chinup.views.input', name='input'), 15 | 16 | url(r'^', include('pin_passcode.urls')), 17 | url(r'^', include('stats.urls')), 18 | url(r'^', include('correlations.urls')), 19 | ) 20 | 21 | if settings.DEBUG: 22 | urlpatterns += patterns('', 23 | # static files (images, css, javascript, etc.) 24 | url(r'^media/(?P.*)$', 'django.views.static.serve', {'document_root': settings.MEDIA_ROOT}), 25 | ) 26 | 27 | import debug_toolbar 28 | urlpatterns += patterns('', 29 | url(r'^__debug__/', include(debug_toolbar.urls)), 30 | ) 31 | -------------------------------------------------------------------------------- /polished_backend.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | import tempfile 3 | 4 | from polished.backends import DjangoBackend 5 | 6 | 7 | class ChinupDjangoBackend(DjangoBackend): 8 | TEMP_SCRIPT = None 9 | 10 | def __init__(self, *args, **kwargs): 11 | super(ChinupDjangoBackend, self).__init__(*args, **kwargs) 12 | script_data = open("generate_data.py", "r").read() 13 | 14 | self.TEMP_SCRIPT = tempfile.NamedTemporaryFile() 15 | self.TEMP_SCRIPT.write(script_data) 16 | 17 | def prepare_page(self, *args, **kwargs): 18 | super(ChinupDjangoBackend, self).prepare_page(*args, **kwargs) 19 | 20 | print 'PREPARING PAGE %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%' 21 | 22 | try: 23 | #subprocess.call(["python", "manage.py", "shell", "<", self.SCRIPT]) 24 | subprocess.check_call('python manage.py shell < %s' % self.TEMP_SCRIPT.name, shell=True) 25 | except Exception: 26 | pass 27 | 28 | 29 | def dispose(self, *args, **kwargs): 30 | self.TEMP_SCRIPT.close() 31 | super(ChinupDjangoBackend, self).dispose(*args, **kwargs) 32 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Eric Carmichael 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /project/static/css/main.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding: 70px 0; 3 | } 4 | .navbar-nav.navbar-right:last-child { 5 | margin-right: 0; 6 | } 7 | .metric { 8 | text-align: center; 9 | /* 10 | .meter { 11 | position: relative; 12 | width: 150px; 13 | height: 75px; 14 | margin: 20px auto; 15 | 16 | .dial { 17 | position: absolute; 18 | left: 50%; 19 | bottom: 0; 20 | width: 75px; 21 | height: 1px; 22 | 23 | .lh-transform-origin(left bottom); 24 | 25 | &.dial-position-1 { .lh-rotate(180); } 26 | &.dial-position-2 { .lh-rotate(200); } 27 | &.dial-position-3 { .lh-rotate(220); } 28 | &.dial-position-4 { .lh-rotate(240); } 29 | &.dial-position-5 { .lh-rotate(260); } 30 | &.dial-position-6 { .lh-rotate(280); } 31 | &.dial-position-7 { .lh-rotate(300); } 32 | &.dial-position-8 { .lh-rotate(320); } 33 | &.dial-position-9 { .lh-rotate(340); } 34 | &.dial-position-10 { .lh-rotate(360); } 35 | } 36 | 37 | .dot { 38 | position: absolute; 39 | bottom: 0; 40 | left: 50%; 41 | 42 | margin: 0 0 -4px -4px; 43 | 44 | width: 8px; 45 | height: 8px; 46 | 47 | .lh-border-radius(50%); 48 | } 49 | }*/ 50 | 51 | } 52 | .metric .awful-shape { 53 | background-color: #999999; 54 | } 55 | .metric .bad-shape { 56 | background-color: #999999; 57 | } 58 | .metric .ok-shape { 59 | background-color: #999999; 60 | } 61 | -------------------------------------------------------------------------------- /project/static/css/main.less: -------------------------------------------------------------------------------- 1 | @import "lesshat-prefixed.less"; 2 | 3 | body { 4 | padding: 70px 0; 5 | } 6 | 7 | .navbar-nav.navbar-right:last-child { 8 | margin-right: 0; 9 | } 10 | 11 | .metric { 12 | text-align: center; 13 | 14 | .awful-shape { background-color: rgb(153, 153, 153); } 15 | .bad-shape { background-color: rgb(153, 153, 153); } 16 | .ok-shape { background-color: rgb(153, 153, 153); } 17 | 18 | /* 19 | .meter { 20 | position: relative; 21 | width: 150px; 22 | height: 75px; 23 | margin: 20px auto; 24 | 25 | .dial { 26 | position: absolute; 27 | left: 50%; 28 | bottom: 0; 29 | width: 75px; 30 | height: 1px; 31 | 32 | .lh-transform-origin(left bottom); 33 | 34 | &.dial-position-1 { .lh-rotate(180); } 35 | &.dial-position-2 { .lh-rotate(200); } 36 | &.dial-position-3 { .lh-rotate(220); } 37 | &.dial-position-4 { .lh-rotate(240); } 38 | &.dial-position-5 { .lh-rotate(260); } 39 | &.dial-position-6 { .lh-rotate(280); } 40 | &.dial-position-7 { .lh-rotate(300); } 41 | &.dial-position-8 { .lh-rotate(320); } 42 | &.dial-position-9 { .lh-rotate(340); } 43 | &.dial-position-10 { .lh-rotate(360); } 44 | } 45 | 46 | .dot { 47 | position: absolute; 48 | bottom: 0; 49 | left: 50%; 50 | 51 | margin: 0 0 -4px -4px; 52 | 53 | width: 8px; 54 | height: 8px; 55 | 56 | .lh-border-radius(50%); 57 | } 58 | }*/ 59 | } 60 | -------------------------------------------------------------------------------- /project/apps/correlations/views.py: -------------------------------------------------------------------------------- 1 | from numpy import corrcoef 2 | 3 | from django.shortcuts import render 4 | 5 | from chinup.models import Metric, MetricRecord 6 | 7 | 8 | def correlation_view(request): 9 | # Get all metric names and data 10 | metric_objects = Metric.objects.filter(daily=True).values_list('name') 11 | metric_names = [m[0] for m in metric_objects] 12 | data = {m: [] for m in metric_names} 13 | raw_data = [] 14 | all_measurements = MetricRecord.objects.filter(metric__daily=True) 15 | 16 | for m in all_measurements: 17 | data[m.metric.name].append(m.measurement) 18 | 19 | # Get the longest array so we can pad data 20 | longest_array = 0 21 | 22 | for metric_name, measurements in data.items(): 23 | if len(measurements) > longest_array: 24 | longest_array = len(measurements) 25 | 26 | for metric_name, measurements in data.items(): 27 | # Pad left side of array with 0's where no data was entered 28 | data[metric_name] = [0] * (longest_array - len(data[metric_name])) + data[metric_name] 29 | raw_data.append(data[metric_name]) 30 | 31 | results_raw = corrcoef(raw_data) 32 | metric_results = {} 33 | 34 | for index_1, metric_name_1, in enumerate(data): 35 | for index_2, metric_name_2 in enumerate(data): 36 | if metric_name_1 not in metric_results: 37 | metric_results[metric_name_1] = [] 38 | 39 | if metric_name_1 != metric_name_2: 40 | metric_results[metric_name_1].append((metric_name_1, metric_name_2, results_raw[index_1, index_2]),) 41 | 42 | return render(request, "correlations/correlations.html", { 43 | 'metric_results': metric_results 44 | }) 45 | -------------------------------------------------------------------------------- /project/apps/chinup/migrations/0003_auto__del_field_metricrecord_measurement.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from south.utils import datetime_utils as datetime 3 | from south.db import db 4 | from south.v2 import SchemaMigration 5 | from django.db import models 6 | 7 | 8 | class Migration(SchemaMigration): 9 | 10 | def forwards(self, orm): 11 | # Deleting field 'MetricRecord.measurement' 12 | db.delete_column(u'chinup_metricrecord', 'measurement') 13 | 14 | 15 | def backwards(self, orm): 16 | # Adding field 'MetricRecord.measurement' 17 | db.add_column(u'chinup_metricrecord', 'measurement', 18 | self.gf('django.db.models.fields.IntegerField')(default=None), 19 | keep_default=False) 20 | 21 | 22 | models = { 23 | u'chinup.metric': { 24 | 'Meta': {'object_name': 'Metric'}, 25 | 'daily': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), 26 | 'description_best': ('django.db.models.fields.TextField', [], {}), 27 | 'description_worst': ('django.db.models.fields.TextField', [], {}), 28 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 29 | 'monthly': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 30 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) 31 | }, 32 | u'chinup.metricrecord': { 33 | 'Meta': {'object_name': 'MetricRecord'}, 34 | 'datetime': ('django.db.models.fields.DateTimeField', [], {}), 35 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 36 | 'metric': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['chinup.Metric']"}), 37 | 'notes': ('django.db.models.fields.TextField', [], {}) 38 | } 39 | } 40 | 41 | complete_apps = ['chinup'] -------------------------------------------------------------------------------- /project/apps/chinup/models.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | 3 | from datetime import timedelta 4 | from django.db import models 5 | 6 | 7 | class Metric(models.Model): 8 | name = models.CharField(max_length=100) 9 | description_worst = models.TextField(null=True, blank=True) 10 | description_best = models.TextField(null=True, blank=True) 11 | daily = models.BooleanField(default=True) 12 | monthly = models.BooleanField(default=False) 13 | boolean = models.BooleanField(default=False) 14 | 15 | def how_often_string(self): 16 | if self.daily: 17 | return "daily" 18 | 19 | if self.monthly: 20 | return "monthly" 21 | 22 | def is_boolean_string(self): 23 | if self.boolean: 24 | return "boolean" 25 | else: 26 | return "" 27 | 28 | def average_span(self, day_span=30): 29 | avg = MetricRecord.objects.filter( 30 | metric=self, 31 | datetime__gt=datetime.datetime.today() - timedelta(days=day_span), 32 | datetime__lt=datetime.datetime.today() 33 | ).aggregate(models.Avg('measurement')) 34 | 35 | return avg['measurement__avg'] 36 | 37 | def __unicode__(self): 38 | return "%s (%s) %s" % (self.name, self.how_often_string(), self.is_boolean_string()) 39 | 40 | 41 | class MetricRecord(models.Model): 42 | metric = models.ForeignKey(Metric, unique_for_date="datetime") 43 | datetime = models.DateField() 44 | measurement = models.IntegerField(default=5, blank=True) 45 | notes = models.TextField(null=True, blank=True) 46 | 47 | def save(self, *args, **kwargs): 48 | # Boolean should default to 0, not 5! 49 | if self.pk is None and self.metric.boolean: 50 | self.measurement = 0 51 | 52 | super(MetricRecord, self).save(*args, **kwargs) 53 | 54 | def __unicode__(self): 55 | return "Record for %s %s on %s" % (self.measurement, self.metric.name, self.datetime) 56 | -------------------------------------------------------------------------------- /project/apps/chinup/migrations/0005_auto__chg_field_metricrecord_datetime.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from south.utils import datetime_utils as datetime 3 | from south.db import db 4 | from south.v2 import SchemaMigration 5 | from django.db import models 6 | 7 | 8 | class Migration(SchemaMigration): 9 | 10 | def forwards(self, orm): 11 | 12 | # Changing field 'MetricRecord.datetime' 13 | db.alter_column(u'chinup_metricrecord', 'datetime', self.gf('django.db.models.fields.DateField')()) 14 | 15 | def backwards(self, orm): 16 | 17 | # Changing field 'MetricRecord.datetime' 18 | db.alter_column(u'chinup_metricrecord', 'datetime', self.gf('django.db.models.fields.DateTimeField')()) 19 | 20 | models = { 21 | u'chinup.metric': { 22 | 'Meta': {'object_name': 'Metric'}, 23 | 'daily': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), 24 | 'description_best': ('django.db.models.fields.TextField', [], {}), 25 | 'description_worst': ('django.db.models.fields.TextField', [], {}), 26 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 27 | 'monthly': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 28 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) 29 | }, 30 | u'chinup.metricrecord': { 31 | 'Meta': {'object_name': 'MetricRecord'}, 32 | 'datetime': ('django.db.models.fields.DateField', [], {}), 33 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 34 | 'measurement': ('django.db.models.fields.IntegerField', [], {'default': '5', 'blank': 'True'}), 35 | 'metric': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['chinup.Metric']"}), 36 | 'notes': ('django.db.models.fields.TextField', [], {}) 37 | } 38 | } 39 | 40 | complete_apps = ['chinup'] -------------------------------------------------------------------------------- /project/apps/chinup/migrations/0002_auto__add_field_metricrecord_metric.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from south.utils import datetime_utils as datetime 3 | from south.db import db 4 | from south.v2 import SchemaMigration 5 | from django.db import models 6 | 7 | 8 | class Migration(SchemaMigration): 9 | 10 | def forwards(self, orm): 11 | # Adding field 'MetricRecord.metric' 12 | db.add_column(u'chinup_metricrecord', 'metric', 13 | self.gf('django.db.models.fields.related.ForeignKey')(default=None, to=orm['chinup.Metric']), 14 | keep_default=False) 15 | 16 | 17 | def backwards(self, orm): 18 | # Deleting field 'MetricRecord.metric' 19 | db.delete_column(u'chinup_metricrecord', 'metric_id') 20 | 21 | 22 | models = { 23 | u'chinup.metric': { 24 | 'Meta': {'object_name': 'Metric'}, 25 | 'daily': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), 26 | 'description_best': ('django.db.models.fields.TextField', [], {}), 27 | 'description_worst': ('django.db.models.fields.TextField', [], {}), 28 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 29 | 'monthly': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 30 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) 31 | }, 32 | u'chinup.metricrecord': { 33 | 'Meta': {'object_name': 'MetricRecord'}, 34 | 'datetime': ('django.db.models.fields.DateTimeField', [], {}), 35 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 36 | 'measurement': ('django.db.models.fields.IntegerField', [], {}), 37 | 'metric': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['chinup.Metric']"}), 38 | 'notes': ('django.db.models.fields.TextField', [], {}) 39 | } 40 | } 41 | 42 | complete_apps = ['chinup'] -------------------------------------------------------------------------------- /project/apps/chinup/migrations/0006_auto__chg_field_metricrecord_notes.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from south.utils import datetime_utils as datetime 3 | from south.db import db 4 | from south.v2 import SchemaMigration 5 | from django.db import models 6 | 7 | 8 | class Migration(SchemaMigration): 9 | 10 | def forwards(self, orm): 11 | 12 | # Changing field 'MetricRecord.notes' 13 | db.alter_column(u'chinup_metricrecord', 'notes', self.gf('django.db.models.fields.TextField')(null=True)) 14 | 15 | def backwards(self, orm): 16 | 17 | # Changing field 'MetricRecord.notes' 18 | db.alter_column(u'chinup_metricrecord', 'notes', self.gf('django.db.models.fields.TextField')(default=None)) 19 | 20 | models = { 21 | u'chinup.metric': { 22 | 'Meta': {'object_name': 'Metric'}, 23 | 'daily': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), 24 | 'description_best': ('django.db.models.fields.TextField', [], {}), 25 | 'description_worst': ('django.db.models.fields.TextField', [], {}), 26 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 27 | 'monthly': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 28 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) 29 | }, 30 | u'chinup.metricrecord': { 31 | 'Meta': {'object_name': 'MetricRecord'}, 32 | 'datetime': ('django.db.models.fields.DateField', [], {}), 33 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 34 | 'measurement': ('django.db.models.fields.IntegerField', [], {'default': '5', 'blank': 'True'}), 35 | 'metric': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['chinup.Metric']"}), 36 | 'notes': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}) 37 | } 38 | } 39 | 40 | complete_apps = ['chinup'] -------------------------------------------------------------------------------- /project/apps/chinup/migrations/0004_auto__add_field_metricrecord_measurement.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from south.utils import datetime_utils as datetime 3 | from south.db import db 4 | from south.v2 import SchemaMigration 5 | from django.db import models 6 | 7 | 8 | class Migration(SchemaMigration): 9 | 10 | def forwards(self, orm): 11 | # Adding field 'MetricRecord.measurement' 12 | db.add_column(u'chinup_metricrecord', 'measurement', 13 | self.gf('django.db.models.fields.IntegerField')(default=5, blank=True), 14 | keep_default=False) 15 | 16 | 17 | def backwards(self, orm): 18 | # Deleting field 'MetricRecord.measurement' 19 | db.delete_column(u'chinup_metricrecord', 'measurement') 20 | 21 | 22 | models = { 23 | u'chinup.metric': { 24 | 'Meta': {'object_name': 'Metric'}, 25 | 'daily': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), 26 | 'description_best': ('django.db.models.fields.TextField', [], {}), 27 | 'description_worst': ('django.db.models.fields.TextField', [], {}), 28 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 29 | 'monthly': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 30 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) 31 | }, 32 | u'chinup.metricrecord': { 33 | 'Meta': {'object_name': 'MetricRecord'}, 34 | 'datetime': ('django.db.models.fields.DateTimeField', [], {}), 35 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 36 | 'measurement': ('django.db.models.fields.IntegerField', [], {'default': '5', 'blank': 'True'}), 37 | 'metric': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['chinup.Metric']"}), 38 | 'notes': ('django.db.models.fields.TextField', [], {}) 39 | } 40 | } 41 | 42 | complete_apps = ['chinup'] -------------------------------------------------------------------------------- /project/apps/chinup/migrations/0007_auto__add_field_metric_boolean.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from south.utils import datetime_utils as datetime 3 | from south.db import db 4 | from south.v2 import SchemaMigration 5 | from django.db import models 6 | 7 | 8 | class Migration(SchemaMigration): 9 | 10 | def forwards(self, orm): 11 | # Adding field 'Metric.boolean' 12 | db.add_column(u'chinup_metric', 'boolean', 13 | self.gf('django.db.models.fields.BooleanField')(default=False), 14 | keep_default=False) 15 | 16 | 17 | def backwards(self, orm): 18 | # Deleting field 'Metric.boolean' 19 | db.delete_column(u'chinup_metric', 'boolean') 20 | 21 | 22 | models = { 23 | u'chinup.metric': { 24 | 'Meta': {'object_name': 'Metric'}, 25 | 'boolean': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 26 | 'daily': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), 27 | 'description_best': ('django.db.models.fields.TextField', [], {}), 28 | 'description_worst': ('django.db.models.fields.TextField', [], {}), 29 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 30 | 'monthly': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 31 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) 32 | }, 33 | u'chinup.metricrecord': { 34 | 'Meta': {'object_name': 'MetricRecord'}, 35 | 'datetime': ('django.db.models.fields.DateField', [], {}), 36 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 37 | 'measurement': ('django.db.models.fields.IntegerField', [], {'default': '5', 'blank': 'True'}), 38 | 'metric': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['chinup.Metric']"}), 39 | 'notes': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}) 40 | } 41 | } 42 | 43 | complete_apps = ['chinup'] -------------------------------------------------------------------------------- /project/apps/chinup/templates/chinup/home.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 |
5 |
6 |
7 |
8 |

Daily {{ daily_metric_span }} day avg

9 | 10 | 7 day - 11 | 14 day - 12 | 30 day 13 | 14 |
15 | 16 |
17 | {% for avg, metric in daily_metrics %} 18 |
19 | 20 | 21 |

{{ metric.name }}

22 |
23 | {% endfor %} 24 |
25 |
26 | 27 |
28 |
29 |

Monthly 120 day avg

30 |
31 | 32 |
33 | {% for avg, metric in monthly_metrics %} 34 |
35 | 36 | 37 |

{{ metric.name }}

38 |
39 | {% endfor %} 40 |
41 |
42 |
43 | 44 |
45 |
46 |
47 |

Notes 10 most recent

48 |
49 | 50 |
51 | {% for note in notes %} 52 |

{{ note.0 }}
— {{ note.1|timesince }} ago

53 | {% endfor %} 54 |
55 |
56 |
57 |
58 | 59 | {% endblock %} 60 | -------------------------------------------------------------------------------- /project/apps/chinup/migrations/0008_auto__chg_field_metric_description_worst__chg_field_metric_description.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from south.utils import datetime_utils as datetime 3 | from south.db import db 4 | from south.v2 import SchemaMigration 5 | from django.db import models 6 | 7 | 8 | class Migration(SchemaMigration): 9 | 10 | def forwards(self, orm): 11 | 12 | # Changing field 'Metric.description_worst' 13 | db.alter_column(u'chinup_metric', 'description_worst', self.gf('django.db.models.fields.TextField')(null=True)) 14 | 15 | # Changing field 'Metric.description_best' 16 | db.alter_column(u'chinup_metric', 'description_best', self.gf('django.db.models.fields.TextField')(null=True)) 17 | 18 | def backwards(self, orm): 19 | 20 | # Changing field 'Metric.description_worst' 21 | db.alter_column(u'chinup_metric', 'description_worst', self.gf('django.db.models.fields.TextField')(default='')) 22 | 23 | # Changing field 'Metric.description_best' 24 | db.alter_column(u'chinup_metric', 'description_best', self.gf('django.db.models.fields.TextField')(default='')) 25 | 26 | models = { 27 | u'chinup.metric': { 28 | 'Meta': {'object_name': 'Metric'}, 29 | 'boolean': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 30 | 'daily': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), 31 | 'description_best': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), 32 | 'description_worst': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), 33 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 34 | 'monthly': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 35 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) 36 | }, 37 | u'chinup.metricrecord': { 38 | 'Meta': {'object_name': 'MetricRecord'}, 39 | 'datetime': ('django.db.models.fields.DateField', [], {}), 40 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 41 | 'measurement': ('django.db.models.fields.IntegerField', [], {'default': '5', 'blank': 'True'}), 42 | 'metric': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['chinup.Metric']"}), 43 | 'notes': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}) 44 | } 45 | } 46 | 47 | complete_apps = ['chinup'] -------------------------------------------------------------------------------- /project/apps/chinup/static/js/chinup.js: -------------------------------------------------------------------------------- 1 | var chinupApp = angular.module('chinupApp', ['ngCookies'], function($interpolateProvider) { 2 | $interpolateProvider.startSymbol('[['); 3 | $interpolateProvider.endSymbol(']]'); 4 | }); 5 | 6 | chinupApp 7 | .run(function($rootScope, $log, $http, $cookies) { 8 | $http.defaults.headers.common['X-CSRFToken'] = $('input[name="csrfmiddlewaretoken"]').val(); 9 | }); 10 | 11 | // Get parameters 12 | var get_params = decodeURIComponent(window.location.search.slice(1)) 13 | .split('&') 14 | .reduce(function _reduce (/*Object*/ a, /*String*/ b) { 15 | b = b.split('='); 16 | a[b[0]] = b[1]; 17 | return a; 18 | }, {}); 19 | 20 | function ChinupController($scope, $http, $location) { 21 | $scope.metrics = {}; 22 | $scope.save_text = "Save"; 23 | $scope.date = get_params['date'] || ''; 24 | 25 | $scope.save_metrics = function() { 26 | $scope.save_text = "Saving..."; 27 | 28 | $scope.metric_form['csrfmiddlewaretoken'] = $('input[name="csrfmiddlewaretoken"]').val(); 29 | 30 | console.log($scope.metrics); 31 | 32 | $http({ 33 | method: 'POST', 34 | url: '/input/?date=' + $scope.date, 35 | data: $scope.metrics, 36 | headers: {'X-CSRFToken': $scope.metric_form['csrfmiddlewaretoken'], 'Content-Type': "application/json"} 37 | }) 38 | .success(function(data){ 39 | $scope.save_text = "Saved!"; 40 | }) 41 | .error(function(data){ 42 | $scope.save_text = "Error"; 43 | }); 44 | }; 45 | 46 | $scope.convert_measurement_to_hsl = function(value){ 47 | var hue = ((value * 12) - 12); 48 | var sat = '50%'; 49 | var l = '65%'; 50 | 51 | // Skip gold color, make it gray 52 | if(value == 5) { 53 | sat = '0%'; 54 | } else if (value > 5) { 55 | hue += 24; 56 | } 57 | 58 | return 'hsl(' + hue + ', ' + sat + ', ' + l + ')'; 59 | }; 60 | 61 | $scope.init_recording = function(record_pk, type, value, checklist) { 62 | if($scope.metrics[record_pk] === undefined) { 63 | $scope.metrics[record_pk] = {} 64 | } 65 | 66 | if(checklist == 'checklist' && value) { 67 | value = true; 68 | } 69 | 70 | $scope.metrics[record_pk][type] = value; 71 | 72 | console.log($scope.metrics); 73 | 74 | console.log($scope.metrics[record_pk][type]); 75 | }; 76 | } 77 | -------------------------------------------------------------------------------- /project/apps/chinup/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from south.utils import datetime_utils as datetime 3 | from south.db import db 4 | from south.v2 import SchemaMigration 5 | from django.db import models 6 | 7 | 8 | class Migration(SchemaMigration): 9 | 10 | def forwards(self, orm): 11 | # Adding model 'Metric' 12 | db.create_table(u'chinup_metric', ( 13 | (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), 14 | ('name', self.gf('django.db.models.fields.CharField')(max_length=100)), 15 | ('description_worst', self.gf('django.db.models.fields.TextField')()), 16 | ('description_best', self.gf('django.db.models.fields.TextField')()), 17 | ('daily', self.gf('django.db.models.fields.BooleanField')(default=True)), 18 | ('monthly', self.gf('django.db.models.fields.BooleanField')(default=False)), 19 | )) 20 | db.send_create_signal(u'chinup', ['Metric']) 21 | 22 | # Adding model 'MetricRecord' 23 | db.create_table(u'chinup_metricrecord', ( 24 | (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), 25 | ('datetime', self.gf('django.db.models.fields.DateTimeField')()), 26 | ('measurement', self.gf('django.db.models.fields.IntegerField')()), 27 | ('notes', self.gf('django.db.models.fields.TextField')()), 28 | )) 29 | db.send_create_signal(u'chinup', ['MetricRecord']) 30 | 31 | 32 | def backwards(self, orm): 33 | # Deleting model 'Metric' 34 | db.delete_table(u'chinup_metric') 35 | 36 | # Deleting model 'MetricRecord' 37 | db.delete_table(u'chinup_metricrecord') 38 | 39 | 40 | models = { 41 | u'chinup.metric': { 42 | 'Meta': {'object_name': 'Metric'}, 43 | 'daily': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), 44 | 'description_best': ('django.db.models.fields.TextField', [], {}), 45 | 'description_worst': ('django.db.models.fields.TextField', [], {}), 46 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 47 | 'monthly': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 48 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) 49 | }, 50 | u'chinup.metricrecord': { 51 | 'Meta': {'object_name': 'MetricRecord'}, 52 | 'datetime': ('django.db.models.fields.DateTimeField', [], {}), 53 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 54 | 'measurement': ('django.db.models.fields.IntegerField', [], {}), 55 | 'notes': ('django.db.models.fields.TextField', [], {}) 56 | } 57 | } 58 | 59 | complete_apps = ['chinup'] -------------------------------------------------------------------------------- /project/static/js/jquery.autosize.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | Autosize v1.18.4 - 2014-01-11 3 | Automatically adjust textarea height based on user input. 4 | (c) 2014 Jack Moore - http://www.jacklmoore.com/autosize 5 | license: http://www.opensource.org/licenses/mit-license.php 6 | */ 7 | !function(a){var b,c={className:"autosizejs",append:"",callback:!1,resizeDelay:10,placeholder:!0},d=' 74 | 75 | {% endfor %} 76 | 77 | 78 | 79 | 80 | 81 | 82 | {% if day_of_month == 1 %} 83 |
84 |
85 |
86 |

Monthly Metrics 87 | for {{ date|date:"D M d" }} 88 |

89 | 90 | previous day 91 | - 92 | 93 | next day 94 | 95 |
96 | 97 |
98 | 99 | {% csrf_token %} 100 | {% for record in monthly_metrics %} 101 |
102 |

103 | 108 | 5 109 | 110 | {{ record.metric.name }} 111 |

112 | {{ record.metric.description_worst }} 113 |
114 | {{ record.metric.description_best }} 115 | 116 |
117 | 125 |
126 | 127 | 133 |
134 | {% endfor %} 135 |
136 |
137 |
138 |
139 | {% endif %} 140 | 141 |
142 |
Save
143 |
144 | 145 | {% endblock %} 146 | -------------------------------------------------------------------------------- /project/apps/stats/templates/stats/stats.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% load dictionary %} 4 | {% load static %} 5 | 6 | {% block content %} 7 |
8 | {% if no_measurements_yet %} 9 |

Please add at least a Metric and enter some Metric records!

10 | {% else %} 11 |
12 | 15 | 18 | 21 | 24 | 27 |
28 | 29 |
30 |
31 |
32 |
33 |
34 | {% endif %} 35 |
36 | 37 | 38 | 225 | {% endblock %} 226 | -------------------------------------------------------------------------------- /project/static/js/bootstrap.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v3.0.3 (http://getbootstrap.com) 3 | * Copyright 2013 Twitter, Inc. 4 | * Licensed under http://www.apache.org/licenses/LICENSE-2.0 5 | */ 6 | 7 | if("undefined"==typeof jQuery)throw new Error("Bootstrap requires jQuery");+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]}}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one(a.support.transition.end,function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b()})}(jQuery),+function(a){"use strict";var b='[data-dismiss="alert"]',c=function(c){a(c).on("click",b,this.close)};c.prototype.close=function(b){function c(){f.trigger("closed.bs.alert").remove()}var d=a(this),e=d.attr("data-target");e||(e=d.attr("href"),e=e&&e.replace(/.*(?=#[^\s]*$)/,""));var f=a(e);b&&b.preventDefault(),f.length||(f=d.hasClass("alert")?d:d.parent()),f.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(f.removeClass("in"),a.support.transition&&f.hasClass("fade")?f.one(a.support.transition.end,c).emulateTransitionEnd(150):c())};var d=a.fn.alert;a.fn.alert=function(b){return this.each(function(){var d=a(this),e=d.data("bs.alert");e||d.data("bs.alert",e=new c(this)),"string"==typeof b&&e[b].call(d)})},a.fn.alert.Constructor=c,a.fn.alert.noConflict=function(){return a.fn.alert=d,this},a(document).on("click.bs.alert.data-api",b,c.prototype.close)}(jQuery),+function(a){"use strict";var b=function(c,d){this.$element=a(c),this.options=a.extend({},b.DEFAULTS,d)};b.DEFAULTS={loadingText:"loading..."},b.prototype.setState=function(a){var b="disabled",c=this.$element,d=c.is("input")?"val":"html",e=c.data();a+="Text",e.resetText||c.data("resetText",c[d]()),c[d](e[a]||this.options[a]),setTimeout(function(){"loadingText"==a?c.addClass(b).attr(b,b):c.removeClass(b).removeAttr(b)},0)},b.prototype.toggle=function(){var a=this.$element.closest('[data-toggle="buttons"]'),b=!0;if(a.length){var c=this.$element.find("input");"radio"===c.prop("type")&&(c.prop("checked")&&this.$element.hasClass("active")?b=!1:a.find(".active").removeClass("active")),b&&c.prop("checked",!this.$element.hasClass("active")).trigger("change")}b&&this.$element.toggleClass("active")};var c=a.fn.button;a.fn.button=function(c){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof c&&c;e||d.data("bs.button",e=new b(this,f)),"toggle"==c?e.toggle():c&&e.setState(c)})},a.fn.button.Constructor=b,a.fn.button.noConflict=function(){return a.fn.button=c,this},a(document).on("click.bs.button.data-api","[data-toggle^=button]",function(b){var c=a(b.target);c.hasClass("btn")||(c=c.closest(".btn")),c.button("toggle"),b.preventDefault()})}(jQuery),+function(a){"use strict";var b=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=this.sliding=this.interval=this.$active=this.$items=null,"hover"==this.options.pause&&this.$element.on("mouseenter",a.proxy(this.pause,this)).on("mouseleave",a.proxy(this.cycle,this))};b.DEFAULTS={interval:5e3,pause:"hover",wrap:!0},b.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},b.prototype.getActiveIndex=function(){return this.$active=this.$element.find(".item.active"),this.$items=this.$active.parent().children(),this.$items.index(this.$active)},b.prototype.to=function(b){var c=this,d=this.getActiveIndex();return b>this.$items.length-1||0>b?void 0:this.sliding?this.$element.one("slid.bs.carousel",function(){c.to(b)}):d==b?this.pause().cycle():this.slide(b>d?"next":"prev",a(this.$items[b]))},b.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition.end&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},b.prototype.next=function(){return this.sliding?void 0:this.slide("next")},b.prototype.prev=function(){return this.sliding?void 0:this.slide("prev")},b.prototype.slide=function(b,c){var d=this.$element.find(".item.active"),e=c||d[b](),f=this.interval,g="next"==b?"left":"right",h="next"==b?"first":"last",i=this;if(!e.length){if(!this.options.wrap)return;e=this.$element.find(".item")[h]()}this.sliding=!0,f&&this.pause();var j=a.Event("slide.bs.carousel",{relatedTarget:e[0],direction:g});if(!e.hasClass("active")){if(this.$indicators.length&&(this.$indicators.find(".active").removeClass("active"),this.$element.one("slid.bs.carousel",function(){var b=a(i.$indicators.children()[i.getActiveIndex()]);b&&b.addClass("active")})),a.support.transition&&this.$element.hasClass("slide")){if(this.$element.trigger(j),j.isDefaultPrevented())return;e.addClass(b),e[0].offsetWidth,d.addClass(g),e.addClass(g),d.one(a.support.transition.end,function(){e.removeClass([b,g].join(" ")).addClass("active"),d.removeClass(["active",g].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger("slid.bs.carousel")},0)}).emulateTransitionEnd(600)}else{if(this.$element.trigger(j),j.isDefaultPrevented())return;d.removeClass("active"),e.addClass("active"),this.sliding=!1,this.$element.trigger("slid.bs.carousel")}return f&&this.cycle(),this}};var c=a.fn.carousel;a.fn.carousel=function(c){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},b.DEFAULTS,d.data(),"object"==typeof c&&c),g="string"==typeof c?c:f.slide;e||d.data("bs.carousel",e=new b(this,f)),"number"==typeof c?e.to(c):g?e[g]():f.interval&&e.pause().cycle()})},a.fn.carousel.Constructor=b,a.fn.carousel.noConflict=function(){return a.fn.carousel=c,this},a(document).on("click.bs.carousel.data-api","[data-slide], [data-slide-to]",function(b){var c,d=a(this),e=a(d.attr("data-target")||(c=d.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,"")),f=a.extend({},e.data(),d.data()),g=d.attr("data-slide-to");g&&(f.interval=!1),e.carousel(f),(g=d.attr("data-slide-to"))&&e.data("bs.carousel").to(g),b.preventDefault()}),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var b=a(this);b.carousel(b.data())})})}(jQuery),+function(a){"use strict";var b=function(c,d){this.$element=a(c),this.options=a.extend({},b.DEFAULTS,d),this.transitioning=null,this.options.parent&&(this.$parent=a(this.options.parent)),this.options.toggle&&this.toggle()};b.DEFAULTS={toggle:!0},b.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},b.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var b=a.Event("show.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.$parent&&this.$parent.find("> .panel > .in");if(c&&c.length){var d=c.data("bs.collapse");if(d&&d.transitioning)return;c.collapse("hide"),d||c.data("bs.collapse",null)}var e=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[e](0),this.transitioning=1;var f=function(){this.$element.removeClass("collapsing").addClass("in")[e]("auto"),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return f.call(this);var g=a.camelCase(["scroll",e].join("-"));this.$element.one(a.support.transition.end,a.proxy(f,this)).emulateTransitionEnd(350)[e](this.$element[0][g])}}},b.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse").removeClass("in"),this.transitioning=1;var d=function(){this.transitioning=0,this.$element.trigger("hidden.bs.collapse").removeClass("collapsing").addClass("collapse")};return a.support.transition?(this.$element[c](0).one(a.support.transition.end,a.proxy(d,this)).emulateTransitionEnd(350),void 0):d.call(this)}}},b.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()};var c=a.fn.collapse;a.fn.collapse=function(c){return this.each(function(){var d=a(this),e=d.data("bs.collapse"),f=a.extend({},b.DEFAULTS,d.data(),"object"==typeof c&&c);e||d.data("bs.collapse",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.collapse.Constructor=b,a.fn.collapse.noConflict=function(){return a.fn.collapse=c,this},a(document).on("click.bs.collapse.data-api","[data-toggle=collapse]",function(b){var c,d=a(this),e=d.attr("data-target")||b.preventDefault()||(c=d.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,""),f=a(e),g=f.data("bs.collapse"),h=g?"toggle":d.data(),i=d.attr("data-parent"),j=i&&a(i);g&&g.transitioning||(j&&j.find('[data-toggle=collapse][data-parent="'+i+'"]').not(d).addClass("collapsed"),d[f.hasClass("in")?"addClass":"removeClass"]("collapsed")),f.collapse(h)})}(jQuery),+function(a){"use strict";function b(){a(d).remove(),a(e).each(function(b){var d=c(a(this));d.hasClass("open")&&(d.trigger(b=a.Event("hide.bs.dropdown")),b.isDefaultPrevented()||d.removeClass("open").trigger("hidden.bs.dropdown"))})}function c(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}var d=".dropdown-backdrop",e="[data-toggle=dropdown]",f=function(b){a(b).on("click.bs.dropdown",this.toggle)};f.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=c(e),g=f.hasClass("open");if(b(),!g){if("ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a(''}),b.prototype=a.extend({},a.fn.tooltip.Constructor.prototype),b.prototype.constructor=b,b.prototype.getDefaults=function(){return b.DEFAULTS},b.prototype.setContent=function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.options.html?"html":"text"](b),a.find(".popover-content")[this.options.html?"html":"text"](c),a.removeClass("fade top bottom left right in"),a.find(".popover-title").html()||a.find(".popover-title").hide()},b.prototype.hasContent=function(){return this.getTitle()||this.getContent()},b.prototype.getContent=function(){var a=this.$element,b=this.options;return a.attr("data-content")||("function"==typeof b.content?b.content.call(a[0]):b.content)},b.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".arrow")},b.prototype.tip=function(){return this.$tip||(this.$tip=a(this.options.template)),this.$tip};var c=a.fn.popover;a.fn.popover=function(c){return this.each(function(){var d=a(this),e=d.data("bs.popover"),f="object"==typeof c&&c;e||d.data("bs.popover",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.popover.Constructor=b,a.fn.popover.noConflict=function(){return a.fn.popover=c,this}}(jQuery),+function(a){"use strict";function b(c,d){var e,f=a.proxy(this.process,this);this.$element=a(c).is("body")?a(window):a(c),this.$body=a("body"),this.$scrollElement=this.$element.on("scroll.bs.scroll-spy.data-api",f),this.options=a.extend({},b.DEFAULTS,d),this.selector=(this.options.target||(e=a(c).attr("href"))&&e.replace(/.*(?=#[^\s]+$)/,"")||"")+" .nav li > a",this.offsets=a([]),this.targets=a([]),this.activeTarget=null,this.refresh(),this.process()}b.DEFAULTS={offset:10},b.prototype.refresh=function(){var b=this.$element[0]==window?"offset":"position";this.offsets=a([]),this.targets=a([]);var c=this;this.$body.find(this.selector).map(function(){var d=a(this),e=d.data("target")||d.attr("href"),f=/^#\w/.test(e)&&a(e);return f&&f.length&&[[f[b]().top+(!a.isWindow(c.$scrollElement.get(0))&&c.$scrollElement.scrollTop()),e]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){c.offsets.push(this[0]),c.targets.push(this[1])})},b.prototype.process=function(){var a,b=this.$scrollElement.scrollTop()+this.options.offset,c=this.$scrollElement[0].scrollHeight||this.$body[0].scrollHeight,d=c-this.$scrollElement.height(),e=this.offsets,f=this.targets,g=this.activeTarget;if(b>=d)return g!=(a=f.last()[0])&&this.activate(a);for(a=e.length;a--;)g!=f[a]&&b>=e[a]&&(!e[a+1]||b<=e[a+1])&&this.activate(f[a])},b.prototype.activate=function(b){this.activeTarget=b,a(this.selector).parents(".active").removeClass("active");var c=this.selector+'[data-target="'+b+'"],'+this.selector+'[href="'+b+'"]',d=a(c).parents("li").addClass("active");d.parent(".dropdown-menu").length&&(d=d.closest("li.dropdown").addClass("active")),d.trigger("activate.bs.scrollspy")};var c=a.fn.scrollspy;a.fn.scrollspy=function(c){return this.each(function(){var d=a(this),e=d.data("bs.scrollspy"),f="object"==typeof c&&c;e||d.data("bs.scrollspy",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.scrollspy.Constructor=b,a.fn.scrollspy.noConflict=function(){return a.fn.scrollspy=c,this},a(window).on("load",function(){a('[data-spy="scroll"]').each(function(){var b=a(this);b.scrollspy(b.data())})})}(jQuery),+function(a){"use strict";var b=function(b){this.element=a(b)};b.prototype.show=function(){var b=this.element,c=b.closest("ul:not(.dropdown-menu)"),d=b.data("target");if(d||(d=b.attr("href"),d=d&&d.replace(/.*(?=#[^\s]*$)/,"")),!b.parent("li").hasClass("active")){var e=c.find(".active:last a")[0],f=a.Event("show.bs.tab",{relatedTarget:e});if(b.trigger(f),!f.isDefaultPrevented()){var g=a(d);this.activate(b.parent("li"),c),this.activate(g,g.parent(),function(){b.trigger({type:"shown.bs.tab",relatedTarget:e})})}}},b.prototype.activate=function(b,c,d){function e(){f.removeClass("active").find("> .dropdown-menu > .active").removeClass("active"),b.addClass("active"),g?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu")&&b.closest("li.dropdown").addClass("active"),d&&d()}var f=c.find("> .active"),g=d&&a.support.transition&&f.hasClass("fade");g?f.one(a.support.transition.end,e).emulateTransitionEnd(150):e(),f.removeClass("in")};var c=a.fn.tab;a.fn.tab=function(c){return this.each(function(){var d=a(this),e=d.data("bs.tab");e||d.data("bs.tab",e=new b(this)),"string"==typeof c&&e[c]()})},a.fn.tab.Constructor=b,a.fn.tab.noConflict=function(){return a.fn.tab=c,this},a(document).on("click.bs.tab.data-api",'[data-toggle="tab"], [data-toggle="pill"]',function(b){b.preventDefault(),a(this).tab("show")})}(jQuery),+function(a){"use strict";var b=function(c,d){this.options=a.extend({},b.DEFAULTS,d),this.$window=a(window).on("scroll.bs.affix.data-api",a.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",a.proxy(this.checkPositionWithEventLoop,this)),this.$element=a(c),this.affixed=this.unpin=null,this.checkPosition()};b.RESET="affix affix-top affix-bottom",b.DEFAULTS={offset:0},b.prototype.checkPositionWithEventLoop=function(){setTimeout(a.proxy(this.checkPosition,this),1)},b.prototype.checkPosition=function(){if(this.$element.is(":visible")){var c=a(document).height(),d=this.$window.scrollTop(),e=this.$element.offset(),f=this.options.offset,g=f.top,h=f.bottom;"object"!=typeof f&&(h=g=f),"function"==typeof g&&(g=f.top()),"function"==typeof h&&(h=f.bottom());var i=null!=this.unpin&&d+this.unpin<=e.top?!1:null!=h&&e.top+this.$element.height()>=c-h?"bottom":null!=g&&g>=d?"top":!1;this.affixed!==i&&(this.unpin&&this.$element.css("top",""),this.affixed=i,this.unpin="bottom"==i?e.top-d:null,this.$element.removeClass(b.RESET).addClass("affix"+(i?"-"+i:"")),"bottom"==i&&this.$element.offset({top:document.body.offsetHeight-h-this.$element.height()}))}};var c=a.fn.affix;a.fn.affix=function(c){return this.each(function(){var d=a(this),e=d.data("bs.affix"),f="object"==typeof c&&c;e||d.data("bs.affix",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.affix.Constructor=b,a.fn.affix.noConflict=function(){return a.fn.affix=c,this},a(window).on("load",function(){a('[data-spy="affix"]').each(function(){var b=a(this),c=b.data();c.offset=c.offset||{},c.offsetBottom&&(c.offset.bottom=c.offsetBottom),c.offsetTop&&(c.offset.top=c.offsetTop),b.affix(c)})})}(jQuery); -------------------------------------------------------------------------------- /project/static/js/gauge.min.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.6.3 2 | (function() { 3 | var AnimatedText, AnimatedTextFactory, Bar, BaseDonut, BaseGauge, Donut, Gauge, GaugePointer, TextRenderer, ValueUpdater, addCommas, cutHex, formatNumber, mergeObjects, secondsToString, updateObjectValues, _ref, _ref1, 4 | __hasProp = {}.hasOwnProperty, 5 | __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; 6 | 7 | (function() { 8 | var browserRequestAnimationFrame, isCancelled, lastId, vendor, vendors, _i, _len; 9 | vendors = ['ms', 'moz', 'webkit', 'o']; 10 | for (_i = 0, _len = vendors.length; _i < _len; _i++) { 11 | vendor = vendors[_i]; 12 | if (window.requestAnimationFrame) { 13 | break; 14 | } 15 | window.requestAnimationFrame = window[vendor + 'RequestAnimationFrame']; 16 | window.cancelAnimationFrame = window[vendor + 'CancelAnimationFrame'] || window[vendor + 'CancelRequestAnimationFrame']; 17 | } 18 | browserRequestAnimationFrame = null; 19 | lastId = 0; 20 | isCancelled = {}; 21 | if (!requestAnimationFrame) { 22 | window.requestAnimationFrame = function(callback, element) { 23 | var currTime, id, lastTime, timeToCall; 24 | currTime = new Date().getTime(); 25 | timeToCall = Math.max(0, 16 - (currTime - lastTime)); 26 | id = window.setTimeout(function() { 27 | return callback(currTime + timeToCall); 28 | }, timeToCall); 29 | lastTime = currTime + timeToCall; 30 | return id; 31 | }; 32 | return window.cancelAnimationFrame = function(id) { 33 | return clearTimeout(id); 34 | }; 35 | } else if (!window.cancelAnimationFrame) { 36 | browserRequestAnimationFrame = window.requestAnimationFrame; 37 | window.requestAnimationFrame = function(callback, element) { 38 | var myId; 39 | myId = ++lastId; 40 | browserRequestAnimationFrame(function() { 41 | if (!isCancelled[myId]) { 42 | return callback(); 43 | } 44 | }, element); 45 | return myId; 46 | }; 47 | return window.cancelAnimationFrame = function(id) { 48 | return isCancelled[id] = true; 49 | }; 50 | } 51 | })(); 52 | 53 | String.prototype.hashCode = function() { 54 | var char, hash, i, _i, _ref; 55 | hash = 0; 56 | if (this.length === 0) { 57 | return hash; 58 | } 59 | for (i = _i = 0, _ref = this.length; 0 <= _ref ? _i < _ref : _i > _ref; i = 0 <= _ref ? ++_i : --_i) { 60 | char = this.charCodeAt(i); 61 | hash = ((hash << 5) - hash) + char; 62 | hash = hash & hash; 63 | } 64 | return hash; 65 | }; 66 | 67 | secondsToString = function(sec) { 68 | var hr, min; 69 | hr = Math.floor(sec / 3600); 70 | min = Math.floor((sec - (hr * 3600)) / 60); 71 | sec -= (hr * 3600) + (min * 60); 72 | sec += ''; 73 | min += ''; 74 | while (min.length < 2) { 75 | min = '0' + min; 76 | } 77 | while (sec.length < 2) { 78 | sec = '0' + sec; 79 | } 80 | hr = hr ? hr + ':' : ''; 81 | return hr + min + ':' + sec; 82 | }; 83 | 84 | formatNumber = function(num) { 85 | return addCommas(num.toFixed(0)); 86 | }; 87 | 88 | updateObjectValues = function(obj1, obj2) { 89 | var key, val; 90 | for (key in obj2) { 91 | if (!__hasProp.call(obj2, key)) continue; 92 | val = obj2[key]; 93 | obj1[key] = val; 94 | } 95 | return obj1; 96 | }; 97 | 98 | mergeObjects = function(obj1, obj2) { 99 | var key, out, val; 100 | out = {}; 101 | for (key in obj1) { 102 | if (!__hasProp.call(obj1, key)) continue; 103 | val = obj1[key]; 104 | out[key] = val; 105 | } 106 | for (key in obj2) { 107 | if (!__hasProp.call(obj2, key)) continue; 108 | val = obj2[key]; 109 | out[key] = val; 110 | } 111 | return out; 112 | }; 113 | 114 | addCommas = function(nStr) { 115 | var rgx, x, x1, x2; 116 | nStr += ''; 117 | x = nStr.split('.'); 118 | x1 = x[0]; 119 | x2 = ''; 120 | if (x.length > 1) { 121 | x2 = '.' + x[1]; 122 | } 123 | rgx = /(\d+)(\d{3})/; 124 | while (rgx.test(x1)) { 125 | x1 = x1.replace(rgx, '$1' + ',' + '$2'); 126 | } 127 | return x1 + x2; 128 | }; 129 | 130 | cutHex = function(nStr) { 131 | if (nStr.charAt(0) === "#") { 132 | return nStr.substring(1, 7); 133 | } 134 | return nStr; 135 | }; 136 | 137 | ValueUpdater = (function() { 138 | ValueUpdater.prototype.animationSpeed = 32; 139 | 140 | function ValueUpdater(addToAnimationQueue, clear) { 141 | if (addToAnimationQueue == null) { 142 | addToAnimationQueue = true; 143 | } 144 | this.clear = clear != null ? clear : true; 145 | if (addToAnimationQueue) { 146 | AnimationUpdater.add(this); 147 | } 148 | } 149 | 150 | ValueUpdater.prototype.update = function(force) { 151 | var diff; 152 | if (force == null) { 153 | force = false; 154 | } 155 | if (force || this.displayedValue !== this.value) { 156 | if (this.ctx && this.clear) { 157 | this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); 158 | } 159 | diff = this.value - this.displayedValue; 160 | if (Math.abs(diff / this.animationSpeed) <= 0.001) { 161 | this.displayedValue = this.value; 162 | } else { 163 | this.displayedValue = this.displayedValue + diff / this.animationSpeed; 164 | } 165 | this.render(); 166 | return true; 167 | } 168 | return false; 169 | }; 170 | 171 | return ValueUpdater; 172 | 173 | })(); 174 | 175 | BaseGauge = (function(_super) { 176 | __extends(BaseGauge, _super); 177 | 178 | function BaseGauge() { 179 | _ref = BaseGauge.__super__.constructor.apply(this, arguments); 180 | return _ref; 181 | } 182 | 183 | BaseGauge.prototype.displayScale = 1; 184 | 185 | BaseGauge.prototype.setTextField = function(textField) { 186 | return this.textField = textField instanceof TextRenderer ? textField : new TextRenderer(textField); 187 | }; 188 | 189 | BaseGauge.prototype.setMinValue = function(minValue, updateStartValue) { 190 | var gauge, _i, _len, _ref1, _results; 191 | this.minValue = minValue; 192 | if (updateStartValue == null) { 193 | updateStartValue = true; 194 | } 195 | if (updateStartValue) { 196 | this.displayedValue = this.minValue; 197 | _ref1 = this.gp || []; 198 | _results = []; 199 | for (_i = 0, _len = _ref1.length; _i < _len; _i++) { 200 | gauge = _ref1[_i]; 201 | _results.push(gauge.displayedValue = this.minValue); 202 | } 203 | return _results; 204 | } 205 | }; 206 | 207 | BaseGauge.prototype.setOptions = function(options) { 208 | if (options == null) { 209 | options = null; 210 | } 211 | this.options = mergeObjects(this.options, options); 212 | if (this.textField) { 213 | this.textField.el.style.fontSize = options.fontSize + 'px'; 214 | } 215 | if (this.options.angle > .5) { 216 | this.gauge.options.angle = .5; 217 | } 218 | this.configDisplayScale(); 219 | return this; 220 | }; 221 | 222 | BaseGauge.prototype.configDisplayScale = function() { 223 | var backingStorePixelRatio, devicePixelRatio, height, prevDisplayScale, width; 224 | prevDisplayScale = this.displayScale; 225 | if (this.options.highDpiSupport === false) { 226 | delete this.displayScale; 227 | } else { 228 | devicePixelRatio = window.devicePixelRatio || 1; 229 | backingStorePixelRatio = this.ctx.webkitBackingStorePixelRatio || this.ctx.mozBackingStorePixelRatio || this.ctx.msBackingStorePixelRatio || this.ctx.oBackingStorePixelRatio || this.ctx.backingStorePixelRatio || 1; 230 | this.displayScale = devicePixelRatio / backingStorePixelRatio; 231 | } 232 | if (this.displayScale !== prevDisplayScale) { 233 | width = this.canvas.G__width || this.canvas.width; 234 | height = this.canvas.G__height || this.canvas.height; 235 | this.canvas.width = width * this.displayScale; 236 | this.canvas.height = height * this.displayScale; 237 | this.canvas.style.width = "" + width + "px"; 238 | this.canvas.style.height = "" + height + "px"; 239 | this.canvas.G__width = width; 240 | this.canvas.G__height = height; 241 | } 242 | return this; 243 | }; 244 | 245 | return BaseGauge; 246 | 247 | })(ValueUpdater); 248 | 249 | TextRenderer = (function() { 250 | function TextRenderer(el) { 251 | this.el = el; 252 | } 253 | 254 | TextRenderer.prototype.render = function(gauge) { 255 | return this.el.innerHTML = formatNumber(gauge.displayedValue); 256 | }; 257 | 258 | return TextRenderer; 259 | 260 | })(); 261 | 262 | AnimatedText = (function(_super) { 263 | __extends(AnimatedText, _super); 264 | 265 | AnimatedText.prototype.displayedValue = 0; 266 | 267 | AnimatedText.prototype.value = 0; 268 | 269 | AnimatedText.prototype.setVal = function(value) { 270 | return this.value = 1 * value; 271 | }; 272 | 273 | function AnimatedText(elem, text) { 274 | this.elem = elem; 275 | this.text = text != null ? text : false; 276 | this.value = 1 * this.elem.innerHTML; 277 | if (this.text) { 278 | this.value = 0; 279 | } 280 | } 281 | 282 | AnimatedText.prototype.render = function() { 283 | var textVal; 284 | if (this.text) { 285 | textVal = secondsToString(this.displayedValue.toFixed(0)); 286 | } else { 287 | textVal = addCommas(formatNumber(this.displayedValue)); 288 | } 289 | return this.elem.innerHTML = textVal; 290 | }; 291 | 292 | return AnimatedText; 293 | 294 | })(ValueUpdater); 295 | 296 | AnimatedTextFactory = { 297 | create: function(objList) { 298 | var elem, out, _i, _len; 299 | out = []; 300 | for (_i = 0, _len = objList.length; _i < _len; _i++) { 301 | elem = objList[_i]; 302 | out.push(new AnimatedText(elem)); 303 | } 304 | return out; 305 | } 306 | }; 307 | 308 | GaugePointer = (function(_super) { 309 | __extends(GaugePointer, _super); 310 | 311 | GaugePointer.prototype.displayedValue = 0; 312 | 313 | GaugePointer.prototype.value = 0; 314 | 315 | GaugePointer.prototype.options = { 316 | strokeWidth: 0.035, 317 | length: 0.1, 318 | color: "#000000" 319 | }; 320 | 321 | function GaugePointer(gauge) { 322 | this.gauge = gauge; 323 | this.ctx = this.gauge.ctx; 324 | this.canvas = this.gauge.canvas; 325 | GaugePointer.__super__.constructor.call(this, false, false); 326 | this.setOptions(); 327 | } 328 | 329 | GaugePointer.prototype.setOptions = function(options) { 330 | if (options == null) { 331 | options = null; 332 | } 333 | updateObjectValues(this.options, options); 334 | this.length = this.canvas.height * this.options.length; 335 | this.strokeWidth = this.canvas.height * this.options.strokeWidth; 336 | this.maxValue = this.gauge.maxValue; 337 | this.minValue = this.gauge.minValue; 338 | this.animationSpeed = this.gauge.animationSpeed; 339 | return this.options.angle = this.gauge.options.angle; 340 | }; 341 | 342 | GaugePointer.prototype.render = function() { 343 | var angle, centerX, centerY, endX, endY, startX, startY, x, y; 344 | angle = this.gauge.getAngle.call(this, this.displayedValue); 345 | centerX = this.canvas.width / 2; 346 | centerY = this.canvas.height * 0.9; 347 | x = Math.round(centerX + this.length * Math.cos(angle)); 348 | y = Math.round(centerY + this.length * Math.sin(angle)); 349 | startX = Math.round(centerX + this.strokeWidth * Math.cos(angle - Math.PI / 2)); 350 | startY = Math.round(centerY + this.strokeWidth * Math.sin(angle - Math.PI / 2)); 351 | endX = Math.round(centerX + this.strokeWidth * Math.cos(angle + Math.PI / 2)); 352 | endY = Math.round(centerY + this.strokeWidth * Math.sin(angle + Math.PI / 2)); 353 | this.ctx.fillStyle = this.options.color; 354 | this.ctx.beginPath(); 355 | this.ctx.arc(centerX, centerY, this.strokeWidth, 0, Math.PI * 2, true); 356 | this.ctx.fill(); 357 | this.ctx.beginPath(); 358 | this.ctx.moveTo(startX, startY); 359 | this.ctx.lineTo(x, y); 360 | this.ctx.lineTo(endX, endY); 361 | return this.ctx.fill(); 362 | }; 363 | 364 | return GaugePointer; 365 | 366 | })(ValueUpdater); 367 | 368 | Bar = (function() { 369 | function Bar(elem) { 370 | this.elem = elem; 371 | } 372 | 373 | Bar.prototype.updateValues = function(arrValues) { 374 | this.value = arrValues[0]; 375 | this.maxValue = arrValues[1]; 376 | this.avgValue = arrValues[2]; 377 | return this.render(); 378 | }; 379 | 380 | Bar.prototype.render = function() { 381 | var avgPercent, valPercent; 382 | if (this.textField) { 383 | this.textField.text(formatNumber(this.value)); 384 | } 385 | if (this.maxValue === 0) { 386 | this.maxValue = this.avgValue * 2; 387 | } 388 | valPercent = (this.value / this.maxValue) * 100; 389 | avgPercent = (this.avgValue / this.maxValue) * 100; 390 | $(".bar-value", this.elem).css({ 391 | "width": valPercent + "%" 392 | }); 393 | return $(".typical-value", this.elem).css({ 394 | "width": avgPercent + "%" 395 | }); 396 | }; 397 | 398 | return Bar; 399 | 400 | })(); 401 | 402 | Gauge = (function(_super) { 403 | __extends(Gauge, _super); 404 | 405 | Gauge.prototype.elem = null; 406 | 407 | Gauge.prototype.value = [20]; 408 | 409 | Gauge.prototype.maxValue = 80; 410 | 411 | Gauge.prototype.minValue = 0; 412 | 413 | Gauge.prototype.displayedAngle = 0; 414 | 415 | Gauge.prototype.displayedValue = 0; 416 | 417 | Gauge.prototype.lineWidth = 40; 418 | 419 | Gauge.prototype.paddingBottom = 0.1; 420 | 421 | Gauge.prototype.percentColors = null; 422 | 423 | Gauge.prototype.options = { 424 | colorStart: "#6fadcf", 425 | colorStop: void 0, 426 | gradientType: 0, 427 | strokeColor: "#e0e0e0", 428 | pointer: { 429 | length: 0.8, 430 | strokeWidth: 0.035 431 | }, 432 | angle: 0.15, 433 | lineWidth: 0.44, 434 | fontSize: 40, 435 | limitMax: false, 436 | percentColors: [[0.0, "#a9d70b"], [0.50, "#f9c802"], [1.0, "#ff0000"]] 437 | }; 438 | 439 | function Gauge(canvas) { 440 | this.canvas = canvas; 441 | Gauge.__super__.constructor.call(this); 442 | this.percentColors = null; 443 | if (typeof G_vmlCanvasManager !== 'undefined') { 444 | this.canvas = window.G_vmlCanvasManager.initElement(this.canvas); 445 | } 446 | this.ctx = this.canvas.getContext('2d'); 447 | this.gp = [new GaugePointer(this)]; 448 | this.setOptions(); 449 | this.render(); 450 | } 451 | 452 | Gauge.prototype.setOptions = function(options) { 453 | var gauge, _i, _len, _ref1; 454 | if (options == null) { 455 | options = null; 456 | } 457 | Gauge.__super__.setOptions.call(this, options); 458 | this.configPercentColors(); 459 | this.lineWidth = this.canvas.height * (1 - this.paddingBottom) * this.options.lineWidth; 460 | this.radius = this.canvas.height * (1 - this.paddingBottom) - this.lineWidth; 461 | this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); 462 | this.render(); 463 | _ref1 = this.gp; 464 | for (_i = 0, _len = _ref1.length; _i < _len; _i++) { 465 | gauge = _ref1[_i]; 466 | gauge.setOptions(this.options.pointer); 467 | gauge.render(); 468 | } 469 | return this; 470 | }; 471 | 472 | Gauge.prototype.configPercentColors = function() { 473 | var bval, gval, i, rval, _i, _ref1, _results; 474 | this.percentColors = null; 475 | if (this.options.percentColors !== void 0) { 476 | this.percentColors = new Array(); 477 | _results = []; 478 | for (i = _i = 0, _ref1 = this.options.percentColors.length - 1; 0 <= _ref1 ? _i <= _ref1 : _i >= _ref1; i = 0 <= _ref1 ? ++_i : --_i) { 479 | rval = parseInt((cutHex(this.options.percentColors[i][1])).substring(0, 2), 16); 480 | gval = parseInt((cutHex(this.options.percentColors[i][1])).substring(2, 4), 16); 481 | bval = parseInt((cutHex(this.options.percentColors[i][1])).substring(4, 6), 16); 482 | _results.push(this.percentColors[i] = { 483 | pct: this.options.percentColors[i][0], 484 | color: { 485 | r: rval, 486 | g: gval, 487 | b: bval 488 | } 489 | }); 490 | } 491 | return _results; 492 | } 493 | }; 494 | 495 | Gauge.prototype.set = function(value) { 496 | var i, max_hit, val, _i, _j, _len, _ref1; 497 | if (!(value instanceof Array)) { 498 | value = [value]; 499 | } 500 | if (value.length > this.gp.length) { 501 | for (i = _i = 0, _ref1 = value.length - this.gp.length; 0 <= _ref1 ? _i < _ref1 : _i > _ref1; i = 0 <= _ref1 ? ++_i : --_i) { 502 | this.gp.push(new GaugePointer(this)); 503 | } 504 | } 505 | i = 0; 506 | max_hit = false; 507 | for (_j = 0, _len = value.length; _j < _len; _j++) { 508 | val = value[_j]; 509 | if (val > this.maxValue) { 510 | this.maxValue = this.value * 1.1; 511 | max_hit = true; 512 | } 513 | this.gp[i].value = val; 514 | this.gp[i++].setOptions({ 515 | maxValue: this.maxValue, 516 | angle: this.options.angle 517 | }); 518 | } 519 | this.value = value[value.length - 1]; 520 | if (max_hit) { 521 | if (!this.options.limitMax) { 522 | return AnimationUpdater.run(); 523 | } 524 | } else { 525 | return AnimationUpdater.run(); 526 | } 527 | }; 528 | 529 | Gauge.prototype.getAngle = function(value) { 530 | return (1 + this.options.angle) * Math.PI + ((value - this.minValue) / (this.maxValue - this.minValue)) * (1 - this.options.angle * 2) * Math.PI; 531 | }; 532 | 533 | Gauge.prototype.getColorForPercentage = function(pct, grad) { 534 | var color, endColor, i, rangePct, startColor, _i, _ref1; 535 | if (pct === 0) { 536 | color = this.percentColors[0].color; 537 | } else { 538 | color = this.percentColors[this.percentColors.length - 1].color; 539 | for (i = _i = 0, _ref1 = this.percentColors.length - 1; 0 <= _ref1 ? _i <= _ref1 : _i >= _ref1; i = 0 <= _ref1 ? ++_i : --_i) { 540 | if (pct <= this.percentColors[i].pct) { 541 | if (grad === true) { 542 | startColor = this.percentColors[i - 1]; 543 | endColor = this.percentColors[i]; 544 | rangePct = (pct - startColor.pct) / (endColor.pct - startColor.pct); 545 | color = { 546 | r: Math.floor(startColor.color.r * (1 - rangePct) + endColor.color.r * rangePct), 547 | g: Math.floor(startColor.color.g * (1 - rangePct) + endColor.color.g * rangePct), 548 | b: Math.floor(startColor.color.b * (1 - rangePct) + endColor.color.b * rangePct) 549 | }; 550 | } else { 551 | color = this.percentColors[i].color; 552 | } 553 | break; 554 | } 555 | } 556 | } 557 | return 'rgb(' + [color.r, color.g, color.b].join(',') + ')'; 558 | }; 559 | 560 | Gauge.prototype.getColorForValue = function(val, grad) { 561 | var pct; 562 | pct = (val - this.minValue) / (this.maxValue - this.minValue); 563 | return this.getColorForPercentage(pct, grad); 564 | }; 565 | 566 | Gauge.prototype.render = function() { 567 | var displayedAngle, fillStyle, gauge, h, w, _i, _len, _ref1, _results; 568 | w = this.canvas.width / 2; 569 | h = this.canvas.height * (1 - this.paddingBottom); 570 | displayedAngle = this.getAngle(this.displayedValue); 571 | if (this.textField) { 572 | this.textField.render(this); 573 | } 574 | this.ctx.lineCap = "butt"; 575 | if (this.options.customFillStyle !== void 0) { 576 | fillStyle = this.options.customFillStyle(this); 577 | } else if (this.percentColors !== null) { 578 | fillStyle = this.getColorForValue(this.displayedValue, true); 579 | } else if (this.options.colorStop !== void 0) { 580 | if (this.options.gradientType === 0) { 581 | fillStyle = this.ctx.createRadialGradient(w, h, 9, w, h, 70); 582 | } else { 583 | fillStyle = this.ctx.createLinearGradient(0, 0, w, 0); 584 | } 585 | fillStyle.addColorStop(0, this.options.colorStart); 586 | fillStyle.addColorStop(1, this.options.colorStop); 587 | } else { 588 | fillStyle = this.options.colorStart; 589 | } 590 | this.ctx.strokeStyle = fillStyle; 591 | this.ctx.beginPath(); 592 | this.ctx.arc(w, h, this.radius, (1 + this.options.angle) * Math.PI, displayedAngle, false); 593 | this.ctx.lineWidth = this.lineWidth; 594 | this.ctx.stroke(); 595 | this.ctx.strokeStyle = this.options.strokeColor; 596 | this.ctx.beginPath(); 597 | this.ctx.arc(w, h, this.radius, displayedAngle, (2 - this.options.angle) * Math.PI, false); 598 | this.ctx.stroke(); 599 | _ref1 = this.gp; 600 | _results = []; 601 | for (_i = 0, _len = _ref1.length; _i < _len; _i++) { 602 | gauge = _ref1[_i]; 603 | _results.push(gauge.update(true)); 604 | } 605 | return _results; 606 | }; 607 | 608 | return Gauge; 609 | 610 | })(BaseGauge); 611 | 612 | BaseDonut = (function(_super) { 613 | __extends(BaseDonut, _super); 614 | 615 | BaseDonut.prototype.lineWidth = 15; 616 | 617 | BaseDonut.prototype.displayedValue = 0; 618 | 619 | BaseDonut.prototype.value = 33; 620 | 621 | BaseDonut.prototype.maxValue = 80; 622 | 623 | BaseDonut.prototype.minValue = 0; 624 | 625 | BaseDonut.prototype.options = { 626 | lineWidth: 0.10, 627 | colorStart: "#6f6ea0", 628 | colorStop: "#c0c0db", 629 | strokeColor: "#eeeeee", 630 | shadowColor: "#d5d5d5", 631 | angle: 0.35 632 | }; 633 | 634 | function BaseDonut(canvas) { 635 | this.canvas = canvas; 636 | BaseDonut.__super__.constructor.call(this); 637 | if (typeof G_vmlCanvasManager !== 'undefined') { 638 | this.canvas = window.G_vmlCanvasManager.initElement(this.canvas); 639 | } 640 | this.ctx = this.canvas.getContext('2d'); 641 | this.setOptions(); 642 | this.render(); 643 | } 644 | 645 | BaseDonut.prototype.getAngle = function(value) { 646 | return (1 - this.options.angle) * Math.PI + ((value - this.minValue) / (this.maxValue - this.minValue)) * ((2 + this.options.angle) - (1 - this.options.angle)) * Math.PI; 647 | }; 648 | 649 | BaseDonut.prototype.setOptions = function(options) { 650 | if (options == null) { 651 | options = null; 652 | } 653 | BaseDonut.__super__.setOptions.call(this, options); 654 | this.lineWidth = this.canvas.height * this.options.lineWidth; 655 | this.radius = this.canvas.height / 2 - this.lineWidth / 2; 656 | return this; 657 | }; 658 | 659 | BaseDonut.prototype.set = function(value) { 660 | this.value = value; 661 | if (this.value > this.maxValue) { 662 | this.maxValue = this.value * 1.1; 663 | } 664 | return AnimationUpdater.run(); 665 | }; 666 | 667 | BaseDonut.prototype.render = function() { 668 | var displayedAngle, grdFill, h, start, stop, w; 669 | displayedAngle = this.getAngle(this.displayedValue); 670 | w = this.canvas.width / 2; 671 | h = this.canvas.height / 2; 672 | if (this.textField) { 673 | this.textField.render(this); 674 | } 675 | grdFill = this.ctx.createRadialGradient(w, h, 39, w, h, 70); 676 | grdFill.addColorStop(0, this.options.colorStart); 677 | grdFill.addColorStop(1, this.options.colorStop); 678 | start = this.radius - this.lineWidth / 2; 679 | stop = this.radius + this.lineWidth / 2; 680 | this.ctx.strokeStyle = this.options.strokeColor; 681 | this.ctx.beginPath(); 682 | this.ctx.arc(w, h, this.radius, (1 - this.options.angle) * Math.PI, (2 + this.options.angle) * Math.PI, false); 683 | this.ctx.lineWidth = this.lineWidth; 684 | this.ctx.lineCap = "round"; 685 | this.ctx.stroke(); 686 | this.ctx.strokeStyle = grdFill; 687 | this.ctx.beginPath(); 688 | this.ctx.arc(w, h, this.radius, (1 - this.options.angle) * Math.PI, displayedAngle, false); 689 | return this.ctx.stroke(); 690 | }; 691 | 692 | return BaseDonut; 693 | 694 | })(BaseGauge); 695 | 696 | Donut = (function(_super) { 697 | __extends(Donut, _super); 698 | 699 | function Donut() { 700 | _ref1 = Donut.__super__.constructor.apply(this, arguments); 701 | return _ref1; 702 | } 703 | 704 | Donut.prototype.strokeGradient = function(w, h, start, stop) { 705 | var grd; 706 | grd = this.ctx.createRadialGradient(w, h, start, w, h, stop); 707 | grd.addColorStop(0, this.options.shadowColor); 708 | grd.addColorStop(0.12, this.options._orgStrokeColor); 709 | grd.addColorStop(0.88, this.options._orgStrokeColor); 710 | grd.addColorStop(1, this.options.shadowColor); 711 | return grd; 712 | }; 713 | 714 | Donut.prototype.setOptions = function(options) { 715 | var h, start, stop, w; 716 | if (options == null) { 717 | options = null; 718 | } 719 | Donut.__super__.setOptions.call(this, options); 720 | w = this.canvas.width / 2; 721 | h = this.canvas.height / 2; 722 | start = this.radius - this.lineWidth / 2; 723 | stop = this.radius + this.lineWidth / 2; 724 | this.options._orgStrokeColor = this.options.strokeColor; 725 | this.options.strokeColor = this.strokeGradient(w, h, start, stop); 726 | return this; 727 | }; 728 | 729 | return Donut; 730 | 731 | })(BaseDonut); 732 | 733 | window.AnimationUpdater = { 734 | elements: [], 735 | animId: null, 736 | addAll: function(list) { 737 | var elem, _i, _len, _results; 738 | _results = []; 739 | for (_i = 0, _len = list.length; _i < _len; _i++) { 740 | elem = list[_i]; 741 | _results.push(AnimationUpdater.elements.push(elem)); 742 | } 743 | return _results; 744 | }, 745 | add: function(object) { 746 | return AnimationUpdater.elements.push(object); 747 | }, 748 | run: function() { 749 | var animationFinished, elem, _i, _len, _ref2; 750 | animationFinished = true; 751 | _ref2 = AnimationUpdater.elements; 752 | for (_i = 0, _len = _ref2.length; _i < _len; _i++) { 753 | elem = _ref2[_i]; 754 | if (elem.update()) { 755 | animationFinished = false; 756 | } 757 | } 758 | if (!animationFinished) { 759 | return AnimationUpdater.animId = requestAnimationFrame(AnimationUpdater.run); 760 | } else { 761 | return cancelAnimationFrame(AnimationUpdater.animId); 762 | } 763 | } 764 | }; 765 | 766 | window.Gauge = Gauge; 767 | 768 | window.Donut = Donut; 769 | 770 | window.BaseDonut = BaseDonut; 771 | 772 | window.TextRenderer = TextRenderer; 773 | 774 | }).call(this); 775 | -------------------------------------------------------------------------------- /project/static/fonts/glyphicons-halflings-regular.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | --------------------------------------------------------------------------------