From 5bde8c06cab03d96c596581c3a996bcfaa08ce22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20P=C3=A9ters?= Date: Sun, 8 Nov 2015 15:39:04 +0100 Subject: [PATCH] dataviz: add a gauge cell (#8477) --- README | 10 ++++ combo/apps/dataviz/__init__.py | 26 +++++++++ combo/apps/dataviz/migrations/0001_initial.py | 36 ++++++++++++ .../0002_gauge_jsonp_data_source.py | 20 +++++++ combo/apps/dataviz/migrations/__init__.py | 0 combo/apps/dataviz/models.py | 57 +++++++++++++++++++ combo/apps/dataviz/static/js/combo.gauge.js | 42 ++++++++++++++ combo/apps/dataviz/static/js/gauge.min.js | 1 + .../dataviz/templates/combo/gauge-cell.html | 14 +++++ combo/apps/dataviz/urls.py | 24 ++++++++ combo/apps/dataviz/views.py | 29 ++++++++++ data/themes/gadjo/static/css/agent-portal.css | 17 ++++++ 12 files changed, 276 insertions(+) create mode 100644 combo/apps/dataviz/__init__.py create mode 100644 combo/apps/dataviz/migrations/0001_initial.py create mode 100644 combo/apps/dataviz/migrations/0002_gauge_jsonp_data_source.py create mode 100644 combo/apps/dataviz/migrations/__init__.py create mode 100644 combo/apps/dataviz/models.py create mode 100644 combo/apps/dataviz/static/js/combo.gauge.js create mode 100644 combo/apps/dataviz/static/js/gauge.min.js create mode 100644 combo/apps/dataviz/templates/combo/gauge-cell.html create mode 100644 combo/apps/dataviz/urls.py create mode 100644 combo/apps/dataviz/views.py diff --git a/README b/README index 515641ce..f3c8ad35 100644 --- a/README +++ b/README @@ -126,3 +126,13 @@ details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . + + +Combo embeds some other pieces of code, with their own authors and copyright +notices: + +Gauge.js + Files: combo/apps/dataviz/static/js/gauge.min.js + License: MIT + Comment: + From http://bernii.github.io/gauge.js/ diff --git a/combo/apps/dataviz/__init__.py b/combo/apps/dataviz/__init__.py new file mode 100644 index 00000000..6712bb1c --- /dev/null +++ b/combo/apps/dataviz/__init__.py @@ -0,0 +1,26 @@ +# combo - content management system +# Copyright (C) 2015 Entr'ouvert +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU Affero General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +import django.apps + +class AppConfig(django.apps.AppConfig): + name = 'combo.apps.dataviz' + + def get_before_urls(self): + from . import urls + return urls.urlpatterns + +default_app_config = 'combo.apps.dataviz.AppConfig' diff --git a/combo/apps/dataviz/migrations/0001_initial.py b/combo/apps/dataviz/migrations/0001_initial.py new file mode 100644 index 00000000..d62eaa8e --- /dev/null +++ b/combo/apps/dataviz/migrations/0001_initial.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('auth', '0001_initial'), + ('data', '0012_auto_20151029_1535'), + ] + + operations = [ + migrations.CreateModel( + name='Gauge', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('placeholder', models.CharField(max_length=20)), + ('order', models.PositiveIntegerField()), + ('slug', models.SlugField(verbose_name='Slug', blank=True)), + ('public', models.BooleanField(default=True, verbose_name='Public')), + ('restricted_to_unlogged', models.BooleanField(default=False, verbose_name='Restrict to unlogged users')), + ('title', models.CharField(max_length=150, null=True, verbose_name='Title', blank=True)), + ('url', models.URLField(max_length=150, null=True, verbose_name='URL', blank=True)), + ('data_source', models.CharField(max_length=150, null=True, verbose_name='Data Source', blank=True)), + ('max_value', models.PositiveIntegerField(null=True, verbose_name='Max Value', blank=True)), + ('groups', models.ManyToManyField(to='auth.Group', verbose_name='Groups', blank=True)), + ('page', models.ForeignKey(to='data.Page')), + ], + options={ + 'verbose_name': 'Gauge', + }, + bases=(models.Model,), + ), + ] diff --git a/combo/apps/dataviz/migrations/0002_gauge_jsonp_data_source.py b/combo/apps/dataviz/migrations/0002_gauge_jsonp_data_source.py new file mode 100644 index 00000000..c90aaa3d --- /dev/null +++ b/combo/apps/dataviz/migrations/0002_gauge_jsonp_data_source.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('dataviz', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='gauge', + name='jsonp_data_source', + field=models.BooleanField(default=True, verbose_name='Use JSONP to get data'), + preserve_default=True, + ), + ] diff --git a/combo/apps/dataviz/migrations/__init__.py b/combo/apps/dataviz/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/combo/apps/dataviz/models.py b/combo/apps/dataviz/models.py new file mode 100644 index 00000000..854c3a38 --- /dev/null +++ b/combo/apps/dataviz/models.py @@ -0,0 +1,57 @@ +# combo - content management system +# Copyright (C) 2014-2015 Entr'ouvert +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU Affero General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +from django.core.urlresolvers import reverse +from django.db import models +from django.utils.translation import ugettext_lazy as _ + +from combo.data.models import CellBase +from combo.data.library import register_cell_class + + +@register_cell_class +class Gauge(CellBase): + title = models.CharField(_('Title'), max_length=150, blank=True, null=True) + url = models.URLField(_('URL'), max_length=150, blank=True, null=True) + data_source = models.CharField(_('Data Source'), max_length=150, + blank=True, null=True) + jsonp_data_source = models.BooleanField(_('Use JSONP to get data'), + default=True) + max_value = models.PositiveIntegerField(_('Max Value'), blank=True, null=True) + + template_name = 'combo/gauge-cell.html' + + class Media: + js = ('js/gauge.min.js', 'js/combo.gauge.js') + + class Meta: + verbose_name = _('Gauge') + + def get_additional_label(self): + return self.title + + def get_cell_extra_context(self): + if self.jsonp_data_source: + data_source_url = self.data_source + else: + data_source_url = reverse('combo-ajax-gauge-count', kwargs={'cell': self.id}) + return {'cell': self, + 'title': self.title, + 'url': self.url, + 'max_value': self.max_value, + 'data_source_url': data_source_url, + 'jsonp': self.jsonp_data_source, + } diff --git a/combo/apps/dataviz/static/js/combo.gauge.js b/combo/apps/dataviz/static/js/combo.gauge.js new file mode 100644 index 00000000..6a014440 --- /dev/null +++ b/combo/apps/dataviz/static/js/combo.gauge.js @@ -0,0 +1,42 @@ +$(function() { + var opts = { + lines: 12, // The number of lines to draw + angle: 0.15, // The length of each line + lineWidth: 0.44, // The line thickness + pointer: { + length: 0.9, // The radius of the inner circle + strokeWidth: 0.035, // The rotation offset + color: '#000000' // Fill color + }, + limitMax: 'false', // If true, the pointer will not go past the end of the gauge + colorStart: '#6FADCF', // Colors + colorStop: '#8FC0DA', // just experiment with them + strokeColor: '#E0E0E0', // to see which ones work best for you + percentColors: [[0.0, "#a9d70b" ], [0.8, "#f9c802"], [1.0, "#ff0000"]], + generateGradient: true + }; + $('[data-combo-gauge').each(function(idx, elem) { + var target = $(elem).find('canvas')[0]; + var gauge = new Gauge(target).setOptions(opts); + gauge.maxValue = parseInt($(elem).data('gauge-max-value')) + gauge.set(0); // set actual value + var ajax_params = {dataType: 'json'} + if ($(elem).data('gauge-count-url')) { + ajax_params.url = $(elem).data('gauge-count-url'); + } else { + ajax_params.url = $(elem).data('gauge-count-jsonp-url'); + ajax_params.xhrFields = { withCredentials: true }; + ajax_params.crossDomain = true; + } + ajax_params.success = function(data) { + if (data.count > gauge.maxValue) { + gauge.animationSpeed = 16; + gauge.set(gauge.maxValue); + } else { + gauge.set(data.count); + } + var counter_value = $('' + data.count + '').appendTo(elem); + } + $.ajax(ajax_params); + }); +}); diff --git a/combo/apps/dataviz/static/js/gauge.min.js b/combo/apps/dataviz/static/js/gauge.min.js new file mode 100644 index 00000000..733d502a --- /dev/null +++ b/combo/apps/dataviz/static/js/gauge.min.js @@ -0,0 +1 @@ +(function(){var a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q={}.hasOwnProperty,r=function(a,b){function c(){this.constructor=a}for(var d in b)q.call(b,d)&&(a[d]=b[d]);return c.prototype=b.prototype,a.prototype=new c,a.__super__=b.prototype,a};!function(){var a,b,c,d,e,f,g;for(g=["ms","moz","webkit","o"],c=0,e=g.length;e>c&&(f=g[c],!window.requestAnimationFrame);c++)window.requestAnimationFrame=window[f+"RequestAnimationFrame"],window.cancelAnimationFrame=window[f+"CancelAnimationFrame"]||window[f+"CancelRequestAnimationFrame"];return a=null,d=0,b={},requestAnimationFrame?window.cancelAnimationFrame?void 0:(a=window.requestAnimationFrame,window.requestAnimationFrame=function(c,e){var f;return f=++d,a(function(){return b[f]?void 0:c()},e),f},window.cancelAnimationFrame=function(a){return b[a]=!0}):(window.requestAnimationFrame=function(a,b){var c,d,e,f;return c=(new Date).getTime(),f=Math.max(0,16-(c-e)),d=window.setTimeout(function(){return a(c+f)},f),e=c+f,d},window.cancelAnimationFrame=function(a){return clearTimeout(a)})}(),String.prototype.hashCode=function(){var a,b,c,d,e;if(b=0,0===this.length)return b;for(c=d=0,e=this.length;e>=0?e>d:d>e;c=e>=0?++d:--d)a=this.charCodeAt(c),b=(b<<5)-b+a,b&=b;return b},o=function(a){var b,c;for(b=Math.floor(a/3600),c=Math.floor((a-3600*b)/60),a-=3600*b+60*c,a+="",c+="";c.length<2;)c="0"+c;for(;a.length<2;)a="0"+a;return b=b?b+":":"",b+c+":"+a},m=function(a){return k(a.toFixed(0))},p=function(a,b){var c,d;for(c in b)q.call(b,c)&&(d=b[c],a[c]=d);return a},n=function(a,b){var c,d,e;d={};for(c in a)q.call(a,c)&&(e=a[c],d[c]=e);for(c in b)q.call(b,c)&&(e=b[c],d[c]=e);return d},k=function(a){var b,c,d,e;for(a+="",c=a.split("."),d=c[0],e="",c.length>1&&(e="."+c[1]),b=/(\d+)(\d{3})/;b.test(d);)d=d.replace(b,"$1,$2");return d+e},l=function(a){return"#"===a.charAt(0)?a.substring(1,7):a},j=function(){function a(a,b){null==a&&(a=!0),this.clear=null!=b?b:!0,a&&AnimationUpdater.add(this)}return a.prototype.animationSpeed=32,a.prototype.update=function(a){var b;return null==a&&(a=!1),a||this.displayedValue!==this.value?(this.ctx&&this.clear&&this.ctx.clearRect(0,0,this.canvas.width,this.canvas.height),b=this.value-this.displayedValue,Math.abs(b/this.animationSpeed)<=.001?this.displayedValue=this.value:this.displayedValue=this.displayedValue+b/this.animationSpeed,this.render(),!0):!1},a}(),e=function(a){function b(){return b.__super__.constructor.apply(this,arguments)}return r(b,a),b.prototype.displayScale=1,b.prototype.setTextField=function(a){return this.textField=a instanceof i?a:new i(a)},b.prototype.setMinValue=function(a,b){var c,d,e,f,g;if(this.minValue=a,null==b&&(b=!0),b){for(this.displayedValue=this.minValue,f=this.gp||[],g=[],d=0,e=f.length;e>d;d++)c=f[d],g.push(c.displayedValue=this.minValue);return g}},b.prototype.setOptions=function(a){return null==a&&(a=null),this.options=n(this.options,a),this.textField&&(this.textField.el.style.fontSize=a.fontSize+"px"),this.options.angle>.5&&(this.gauge.options.angle=.5),this.configDisplayScale(),this},b.prototype.configDisplayScale=function(){var a,b,c,d,e;return d=this.displayScale,this.options.highDpiSupport===!1?delete this.displayScale:(b=window.devicePixelRatio||1,a=this.ctx.webkitBackingStorePixelRatio||this.ctx.mozBackingStorePixelRatio||this.ctx.msBackingStorePixelRatio||this.ctx.oBackingStorePixelRatio||this.ctx.backingStorePixelRatio||1,this.displayScale=b/a),this.displayScale!==d&&(e=this.canvas.G__width||this.canvas.width,c=this.canvas.G__height||this.canvas.height,this.canvas.width=e*this.displayScale,this.canvas.height=c*this.displayScale,this.canvas.style.width=e+"px",this.canvas.style.height=c+"px",this.canvas.G__width=e,this.canvas.G__height=c),this},b}(j),i=function(){function a(a){this.el=a}return a.prototype.render=function(a){return this.el.innerHTML=m(a.displayedValue)},a}(),a=function(a){function b(a,b){this.elem=a,this.text=null!=b?b:!1,this.value=1*this.elem.innerHTML,this.text&&(this.value=0)}return r(b,a),b.prototype.displayedValue=0,b.prototype.value=0,b.prototype.setVal=function(a){return this.value=1*a},b.prototype.render=function(){var a;return a=this.text?o(this.displayedValue.toFixed(0)):k(m(this.displayedValue)),this.elem.innerHTML=a},b}(j),b={create:function(b){var c,d,e,f;for(f=[],d=0,e=b.length;e>d;d++)c=b[d],f.push(new a(c));return f}},h=function(a){function b(a){this.gauge=a,this.ctx=this.gauge.ctx,this.canvas=this.gauge.canvas,b.__super__.constructor.call(this,!1,!1),this.setOptions()}return r(b,a),b.prototype.displayedValue=0,b.prototype.value=0,b.prototype.options={strokeWidth:.035,length:.1,color:"#000000"},b.prototype.setOptions=function(a){return null==a&&(a=null),p(this.options,a),this.length=this.canvas.height*this.options.length,this.strokeWidth=this.canvas.height*this.options.strokeWidth,this.maxValue=this.gauge.maxValue,this.minValue=this.gauge.minValue,this.animationSpeed=this.gauge.animationSpeed,this.options.angle=this.gauge.options.angle},b.prototype.render=function(){var a,b,c,d,e,f,g,h,i;return a=this.gauge.getAngle.call(this,this.displayedValue),b=this.canvas.width/2,c=.9*this.canvas.height,h=Math.round(b+this.length*Math.cos(a)),i=Math.round(c+this.length*Math.sin(a)),f=Math.round(b+this.strokeWidth*Math.cos(a-Math.PI/2)),g=Math.round(c+this.strokeWidth*Math.sin(a-Math.PI/2)),d=Math.round(b+this.strokeWidth*Math.cos(a+Math.PI/2)),e=Math.round(c+this.strokeWidth*Math.sin(a+Math.PI/2)),this.ctx.fillStyle=this.options.color,this.ctx.beginPath(),this.ctx.arc(b,c,this.strokeWidth,0,2*Math.PI,!0),this.ctx.fill(),this.ctx.beginPath(),this.ctx.moveTo(f,g),this.ctx.lineTo(h,i),this.ctx.lineTo(d,e),this.ctx.fill()},b}(j),c=function(){function a(a){this.elem=a}return a.prototype.updateValues=function(a){return this.value=a[0],this.maxValue=a[1],this.avgValue=a[2],this.render()},a.prototype.render=function(){var a,b;return this.textField&&this.textField.text(m(this.value)),0===this.maxValue&&(this.maxValue=2*this.avgValue),b=this.value/this.maxValue*100,a=this.avgValue/this.maxValue*100,$(".bar-value",this.elem).css({width:b+"%"}),$(".typical-value",this.elem).css({width:a+"%"})},a}(),g=function(a){function b(a){this.canvas=a,b.__super__.constructor.call(this),this.percentColors=null,"undefined"!=typeof G_vmlCanvasManager&&(this.canvas=window.G_vmlCanvasManager.initElement(this.canvas)),this.ctx=this.canvas.getContext("2d"),this.gp=[new h(this)],this.setOptions(),this.render()}return r(b,a),b.prototype.elem=null,b.prototype.value=[20],b.prototype.maxValue=80,b.prototype.minValue=0,b.prototype.displayedAngle=0,b.prototype.displayedValue=0,b.prototype.lineWidth=40,b.prototype.paddingBottom=.1,b.prototype.percentColors=null,b.prototype.options={colorStart:"#6fadcf",colorStop:void 0,gradientType:0,strokeColor:"#e0e0e0",pointer:{length:.8,strokeWidth:.035},angle:.15,lineWidth:.44,fontSize:40,limitMax:!1},b.prototype.setOptions=function(a){var c,d,e,f;for(null==a&&(a=null),b.__super__.setOptions.call(this,a),this.configPercentColors(),this.lineWidth=this.canvas.height*(1-this.paddingBottom)*this.options.lineWidth,this.radius=this.canvas.height*(1-this.paddingBottom)-this.lineWidth,this.ctx.clearRect(0,0,this.canvas.width,this.canvas.height),this.render(),f=this.gp,d=0,e=f.length;e>d;d++)c=f[d],c.setOptions(this.options.pointer),c.render();return this},b.prototype.configPercentColors=function(){var a,b,c,d,e,f,g;if(this.percentColors=null,void 0!==this.options.percentColors){for(this.percentColors=new Array,f=[],c=d=0,e=this.options.percentColors.length-1;e>=0?e>=d:d>=e;c=e>=0?++d:--d)g=parseInt(l(this.options.percentColors[c][1]).substring(0,2),16),b=parseInt(l(this.options.percentColors[c][1]).substring(2,4),16),a=parseInt(l(this.options.percentColors[c][1]).substring(4,6),16),f.push(this.percentColors[c]={pct:this.options.percentColors[c][0],color:{r:g,g:b,b:a}});return f}},b.prototype.set=function(a){var b,c,d,e,f,g,i;if(a instanceof Array||(a=[a]),a.length>this.gp.length)for(b=c=0,g=a.length-this.gp.length;g>=0?g>c:c>g;b=g>=0?++c:--c)this.gp.push(new h(this));for(b=0,f=!1,d=0,e=a.length;e>d;d++)i=a[d],i>this.maxValue&&(this.maxValue=1.1*this.value,f=!0),this.gp[b].value=i,this.gp[b++].setOptions({maxValue:this.maxValue,angle:this.options.angle});return this.value=a[a.length-1],f&&this.options.limitMax?void 0:AnimationUpdater.run()},b.prototype.getAngle=function(a){return(1+this.options.angle)*Math.PI+(a-this.minValue)/(this.maxValue-this.minValue)*(1-2*this.options.angle)*Math.PI},b.prototype.getColorForPercentage=function(a,b){var c,d,e,f,g,h,i;if(0===a)c=this.percentColors[0].color;else for(c=this.percentColors[this.percentColors.length-1].color,e=f=0,h=this.percentColors.length-1;h>=0?h>=f:f>=h;e=h>=0?++f:--f)if(a<=this.percentColors[e].pct){b===!0?(i=this.percentColors[e-1],d=this.percentColors[e],g=(a-i.pct)/(d.pct-i.pct),c={r:Math.floor(i.color.r*(1-g)+d.color.r*g),g:Math.floor(i.color.g*(1-g)+d.color.g*g),b:Math.floor(i.color.b*(1-g)+d.color.b*g)}):c=this.percentColors[e].color;break}return"rgb("+[c.r,c.g,c.b].join(",")+")"},b.prototype.getColorForValue=function(a,b){var c;return c=(a-this.minValue)/(this.maxValue-this.minValue),this.getColorForPercentage(c,b)},b.prototype.render=function(){var a,b,c,d,e,f,g,h,i;for(i=this.canvas.width/2,d=this.canvas.height*(1-this.paddingBottom),a=this.getAngle(this.displayedValue),this.textField&&this.textField.render(this),this.ctx.lineCap="butt",void 0!==this.options.customFillStyle?b=this.options.customFillStyle(this):null!==this.percentColors?b=this.getColorForValue(this.displayedValue,!0):void 0!==this.options.colorStop?(b=0===this.options.gradientType?this.ctx.createRadialGradient(i,d,9,i,d,70):this.ctx.createLinearGradient(0,0,i,0),b.addColorStop(0,this.options.colorStart),b.addColorStop(1,this.options.colorStop)):b=this.options.colorStart,this.ctx.strokeStyle=b,this.ctx.beginPath(),this.ctx.arc(i,d,this.radius,(1+this.options.angle)*Math.PI,a,!1),this.ctx.lineWidth=this.lineWidth,this.ctx.stroke(),this.ctx.strokeStyle=this.options.strokeColor,this.ctx.beginPath(),this.ctx.arc(i,d,this.radius,a,(2-this.options.angle)*Math.PI,!1),this.ctx.stroke(),g=this.gp,h=[],e=0,f=g.length;f>e;e++)c=g[e],h.push(c.update(!0));return h},b}(e),d=function(a){function b(a){this.canvas=a,b.__super__.constructor.call(this),"undefined"!=typeof G_vmlCanvasManager&&(this.canvas=window.G_vmlCanvasManager.initElement(this.canvas)),this.ctx=this.canvas.getContext("2d"),this.setOptions(),this.render()}return r(b,a),b.prototype.lineWidth=15,b.prototype.displayedValue=0,b.prototype.value=33,b.prototype.maxValue=80,b.prototype.minValue=0,b.prototype.options={lineWidth:.1,colorStart:"#6f6ea0",colorStop:"#c0c0db",strokeColor:"#eeeeee",shadowColor:"#d5d5d5",angle:.35},b.prototype.getAngle=function(a){return(1-this.options.angle)*Math.PI+(a-this.minValue)/(this.maxValue-this.minValue)*(2+this.options.angle-(1-this.options.angle))*Math.PI},b.prototype.setOptions=function(a){return null==a&&(a=null),b.__super__.setOptions.call(this,a),this.lineWidth=this.canvas.height*this.options.lineWidth,this.radius=this.canvas.height/2-this.lineWidth/2,this},b.prototype.set=function(a){return this.value=a,this.value>this.maxValue&&(this.maxValue=1.1*this.value),AnimationUpdater.run()},b.prototype.render=function(){var a,b,c,d,e,f;return a=this.getAngle(this.displayedValue),f=this.canvas.width/2,c=this.canvas.height/2,this.textField&&this.textField.render(this),b=this.ctx.createRadialGradient(f,c,39,f,c,70),b.addColorStop(0,this.options.colorStart),b.addColorStop(1,this.options.colorStop),d=this.radius-this.lineWidth/2,e=this.radius+this.lineWidth/2,this.ctx.strokeStyle=this.options.strokeColor,this.ctx.beginPath(),this.ctx.arc(f,c,this.radius,(1-this.options.angle)*Math.PI,(2+this.options.angle)*Math.PI,!1),this.ctx.lineWidth=this.lineWidth,this.ctx.lineCap="round",this.ctx.stroke(),this.ctx.strokeStyle=b,this.ctx.beginPath(),this.ctx.arc(f,c,this.radius,(1-this.options.angle)*Math.PI,a,!1),this.ctx.stroke()},b}(e),f=function(a){function b(){return b.__super__.constructor.apply(this,arguments)}return r(b,a),b.prototype.strokeGradient=function(a,b,c,d){var e;return e=this.ctx.createRadialGradient(a,b,c,a,b,d),e.addColorStop(0,this.options.shadowColor),e.addColorStop(.12,this.options._orgStrokeColor),e.addColorStop(.88,this.options._orgStrokeColor),e.addColorStop(1,this.options.shadowColor),e},b.prototype.setOptions=function(a){var c,d,e,f;return null==a&&(a=null),b.__super__.setOptions.call(this,a),f=this.canvas.width/2,c=this.canvas.height/2,d=this.radius-this.lineWidth/2,e=this.radius+this.lineWidth/2,this.options._orgStrokeColor=this.options.strokeColor,this.options.strokeColor=this.strokeGradient(f,c,d,e),this},b}(d),window.AnimationUpdater={elements:[],animId:null,addAll:function(a){var b,c,d,e;for(e=[],c=0,d=a.length;d>c;c++)b=a[c],e.push(AnimationUpdater.elements.push(b));return e},add:function(a){return AnimationUpdater.elements.push(a)},run:function(){var a,b,c,d,e;for(a=!0,e=AnimationUpdater.elements,c=0,d=e.length;d>c;c++)b=e[c],b.update()&&(a=!1);return a?cancelAnimationFrame(AnimationUpdater.animId):AnimationUpdater.animId=requestAnimationFrame(AnimationUpdater.run)}},"function"==typeof window.define&&null!=window.define.amd?define(function(){return{Gauge:g,Donut:f,BaseDonut:d,TextRenderer:i}}):"undefined"!=typeof module&&null!=module.exports?module.exports={Gauge:g,Donut:f,BaseDonut:d,TextRenderer:i}:(window.Gauge=g,window.Donut=f,window.BaseDonut=d,window.TextRenderer=i)}).call(this); \ No newline at end of file diff --git a/combo/apps/dataviz/templates/combo/gauge-cell.html b/combo/apps/dataviz/templates/combo/gauge-cell.html new file mode 100644 index 00000000..0e3457d4 --- /dev/null +++ b/combo/apps/dataviz/templates/combo/gauge-cell.html @@ -0,0 +1,14 @@ +
+ + + {% if title %} + {% if url %}{% endif %}{{title}}{% if url %}{% endif %} + {% endif %} +
diff --git a/combo/apps/dataviz/urls.py b/combo/apps/dataviz/urls.py new file mode 100644 index 00000000..011cb0ba --- /dev/null +++ b/combo/apps/dataviz/urls.py @@ -0,0 +1,24 @@ +# combo - content management system +# Copyright (C) 2014-2015 Entr'ouvert +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU Affero General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +from django.conf.urls import patterns, url + +from .views import ajax_gauge_count + +urlpatterns = patterns('', + url(r'^ajax/gauge-count/(?P[\w_-]+)/$', + ajax_gauge_count, name='combo-ajax-gauge-count'), +) diff --git a/combo/apps/dataviz/views.py b/combo/apps/dataviz/views.py new file mode 100644 index 00000000..4fafda66 --- /dev/null +++ b/combo/apps/dataviz/views.py @@ -0,0 +1,29 @@ +# combo - content management system +# Copyright (C) 2015 Entr'ouvert +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU Affero General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +import json +import requests + +from django.conf import settings +from django.http import Http404, HttpResponse + +from .models import Gauge + + +def ajax_gauge_count(request, *args, **kwargs): + gauge = Gauge.objects.get(id=kwargs['cell']) + response = requests.get(gauge.data_source) + return HttpResponse(response.content, content_type='text/json') diff --git a/data/themes/gadjo/static/css/agent-portal.css b/data/themes/gadjo/static/css/agent-portal.css index 92699bf6..76bb5a02 100644 --- a/data/themes/gadjo/static/css/agent-portal.css +++ b/data/themes/gadjo/static/css/agent-portal.css @@ -18,3 +18,20 @@ div.welcome { div.textcell { clear: both; } + +#content div.cell.gauge { + width: 270px; + max-width: 32%; + float: left; +} + +div.cell.gauge div.bo-block { + position: relative; +} + +div.cell.gauge div.bo-block span.counter { + position: absolute; + bottom: 0; + right: 1ex; + font-size: 300%; +}