├── .github └── workflows │ └── stale.yml ├── .gitignore ├── Gruntfile.js ├── LICENSE ├── README.md ├── bower.json ├── dist └── angular-raphael-gauge.min.js ├── example └── index.html ├── package.json └── src └── angular-raphael-gauge.js /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | name: Close inactive issues 2 | on: 3 | schedule: 4 | - cron: "30 1 * * *" 5 | 6 | jobs: 7 | close-issues: 8 | runs-on: ubuntu-latest 9 | permissions: 10 | issues: write 11 | pull-requests: write 12 | steps: 13 | - uses: actions/stale@v5 14 | with: 15 | days-before-issue-stale: 30 16 | days-before-issue-close: 14 17 | stale-issue-label: "stale" 18 | stale-issue-message: "This issue is stale because it has been open for 30 days with no activity." 19 | close-issue-message: "This issue was closed because it has been inactive for 14 days since being marked as stale." 20 | days-before-pr-stale: 60 21 | days-before-pr-close: 14 22 | repo-token: ${{ secrets.GITHUB_TOKEN }} 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | bower_components 3 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | 3 | // Project configuration. 4 | grunt.initConfig({ 5 | pkg: grunt.file.readJSON('package.json'), 6 | uglify: { 7 | options: { 8 | mangle: false, 9 | sourceMap: false, 10 | compress: { 11 | drop_console: true 12 | } 13 | }, 14 | js: { 15 | files: { 16 | 'dist/angular-raphael-gauge.min.js': 'src/angular-raphael-gauge.js' 17 | } 18 | } 19 | } 20 | }); 21 | 22 | // Load the plugin that provides the "uglify" task. 23 | grunt.loadNpmTasks('grunt-contrib-uglify'); 24 | 25 | grunt.registerTask('default', ['uglify']); 26 | }; 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Piotr Wasilewski 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, 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, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | jQuery Raphael Gauge 2 | ============= 3 | [![Code Climate](https://codeclimate.com/github/wasilak/angular-raphael-gauge/badges/gpa.svg)](https://codeclimate.com/github/wasilak/angular-raphael-gauge) 4 | 5 | You can see demo [here](http://wasilak.github.io/angular-raphael-gauge/). 6 | 7 | Why bother? 8 | ------------------- 9 | I needed this kind of Gauge "chart" for one of my projects and it was great opportunity to get some experience in both AngularJS and Raphael.js :) 10 | 11 | INSTALLATION 12 | ------------------- 13 | 14 | via bower: 15 | 16 | ``` 17 | bower install angular-raphael-gauge 18 | ``` 19 | 20 | or simply download latest source code from repository: [link](https://github.com/wasilak/angular-raphael-gauge/archive/master.zip) 21 | 22 | USAGE 23 | ------------------- 24 | 25 | Include ```angular-raphael-gauge``` in your HTML file. Remember about including [Raphael.js](http://raphaeljs.com/) and [jQuery](). 26 | 27 | ```html 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | ``` 40 | 41 | Next plugin-in ```angular-raphael-gauge``` into your application: 42 | 43 | ```javascript 44 | var angularDemo = angular.module('angularDemo', ['angular-raphael-gauge']); 45 | ``` 46 | 47 | Now all you have to do is put directive code into HTML file: 48 | ```javascript 49 | 50 | ``` 51 | 52 | and pass some options via config object in ```$scope```: 53 | ```javascript 54 | angularDemo.controller('DemoCtrl', ['$scope', function ($scope) { 55 | $scope.gauge = { 56 | name: 'Some data', 57 | opacity: 0.55, 58 | value: 65, 59 | text: 'some cool data' 60 | }; 61 | }]); 62 | ``` 63 | 64 | Updating ```$scope.value``` will update gauge value and render animation. 65 | 66 | That's it! :) See [demo](http://wasilak.github.io/angular-raphael-gauge/) for working example. 67 | 68 | OPTIONS 69 | ------------------- 70 | 71 | Here are options available to be set during runtime: 72 | 73 | ```javascript 74 | var options = { 75 | name: false, // text under gauge 76 | value: 25, // value 77 | image: false, // path to image (should be square) - it will be under gauge 78 | text: false, // text in the middle of gauge 79 | textColor: '#000000', // text color 80 | arcColor: '#57E0EA', // animated arc color 81 | bgArcColor: '#000', // round background under arc 82 | opacity: false, // arc opacity 83 | duration: 1600, // animation duration 84 | easing: 'bounce' // Raphael easing effect. Don't use backIn or Elastic, they mess up animation :/ 85 | }; 86 | ``` 87 | 88 | Building / Minifing 89 | ---------- 90 | 91 | You can build minified version yourself, by simply using [Grunt](http://gruntjs.com) in project root. 92 | 93 | ```bash 94 | grunt 95 | ``` 96 | 97 | Contributing 98 | -------------- 99 | 100 | 1. Fork it 101 | 2. Create your feature branch (`git checkout -b my-new-feature`) 102 | 3. Commit your changes (`git commit -am 'Add some feature'`) 103 | 4. Push to the branch (`git push origin my-new-feature`) 104 | 5. Create new Pull Request 105 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-raphael-gauge", 3 | "description": "Nice and simple gauge Raphael.js for AngularJS.", 4 | "main": "dist/angular-raphael-gauge.js", 5 | "keywords": ["angularjs", "raphael", "chart", "gauge"], 6 | "homepage": "https://github.com/wasilak/angular-raphael-gauge", 7 | "repository": { 8 | "type": "git", 9 | "url": "git@github.com:wasilak/angular-raphael-gauge.git" 10 | }, 11 | "license": "MIT", 12 | "authors": [ 13 | "Piotr Wasilewski", "Piotr Wasilewski " 14 | ], 15 | "ignore": [ 16 | ".gitignore", 17 | ".jshintrc", 18 | "Gruntfile.js", 19 | "package.json", 20 | "bower_components" 21 | ], 22 | "dependencies": { 23 | "angular": "~1.2.x", 24 | "raphael": "~2.x", 25 | "jquery": "~1.x" 26 | }, 27 | "devDependencies": {} 28 | } 29 | -------------------------------------------------------------------------------- /dist/angular-raphael-gauge.min.js: -------------------------------------------------------------------------------- 1 | !function(Raphael){"use strict";angular.module("angular-raphael-gauge",[]).directive("raphaelGauge",function(){return{restrict:"EA",scope:{config:"="},template:"
",replace:!0,controller:function($scope,$element){var options={element:$element[0].id,name:!1,value:25,image:!1,icon:!1,text:!1,textColor:"#000000",arcColor:"#57E0EA",bgArcColor:"#000",opacity:!1,duration:1600,easing:"bounce"};options=$.extend(options,$scope.config),$("#"+options.element).html("");var radius=$("#"+options.element).width(),paper=new Raphael(options.element,radius,radius);$("#"+options.element+" svg").css({height:"100%",width:"100%"}),paper.setViewBox(0,0,radius,radius,!0),paper.canvas.setAttribute("preserveAspectRatio","none"),paper.customAttributes.arc=function(xloc,yloc,value,total,R){var path,alpha=360/total*value,a=(90-alpha)*Math.PI/180,x=xloc+R*Math.cos(a),y=yloc-R*Math.sin(a);return path=total===value?[["M",xloc,yloc-R],["A",R,R,0,1,1,xloc-.01,yloc-R]]:[["M",xloc,yloc-R],["A",R,R,0,+(alpha>180),1,x,y]],{path:path}};var counter=function(el,n,max){!function loop(){el.html(n+"%"),n++'),$("#"+element).css("font-size",$("#"+element).width()/10+"px"),$("#"+element+"Percentage").css("font-size",$("#"+element).width()/4+"px"),$(window).resize(function(){$("#"+element).css("font-size",$("#"+element).width()/10+"px"),$("#"+element+"Percentage").css("font-size",$("#"+element).width()/4+"px")}),counter($("#"+element+"Percentage"),0,options.value)};if(options.image){paper.image(options.image,0,0,radius,radius)}if(options.text){paper.text(radius/2,radius/2,options.text).attr({"font-size":radius/16,stroke:options.textColor,fill:options.textColor})}var newArc=!1,newArcBg=!1;$scope.$watch("config.value",function(){if(options.value=$scope.config.value,newArc&&newArc.remove(),newArcBg&&newArcBg.remove(),newArcBg=paper.path().attr({"stroke-opacity":options.opacity?options.opacity:"1",stroke:options.bgArcColor,"stroke-width":.1*radius,arc:[radius/2,radius/2,100,100,.425*radius]}),newArc=paper.path().attr({"stroke-opacity":options.opacity?options.opacity:"1",stroke:options.arcColor,"stroke-width":.15*radius,arc:[radius/2,radius/2,0,100,.425*radius]}),newArc.hover(function(){options.opacity&&newArc.animate({"stroke-opacity":"1"},200)},function(){options.opacity&&newArc.animate({"stroke-opacity":options.opacity},200)}),newArc.rotate(0,100,100).animate({arc:[radius/2,radius/2,options.value,100,.425*radius]},options.duration,options.easing),options.name){var textName=options.element+"Text";$("#"+options.element).append('
'),gaugeText(textName,options.name,options.value)}})}}})}(Raphael); -------------------------------------------------------------------------------- /example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Angular Raphael Gauge Demo 5 | 6 | 7 | 8 |

for this demo to work, you have to install dependencies with bower.

9 | 10 |
11 |

Gauge Demo

12 |

13 | Change value: 14 | 15 |

16 |
17 | 18 |
19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-raphael-gauge", 3 | "version": "0.0.1", 4 | "devDependencies": {}, 5 | "dependencies": { 6 | "grunt": "^0.4.5", 7 | "grunt-contrib-uglify": "^0.5.1" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/angular-raphael-gauge.js: -------------------------------------------------------------------------------- 1 | // using Raphael.js (be sure to include it earlier) 2 | ;(function(Raphael) { 3 | 'use strict'; 4 | 5 | // module definition, this has to be included in your app 6 | angular.module('angular-raphael-gauge', []) 7 | // directive definition, if you want to use itm you have to include it in controller 8 | .directive('raphaelGauge', function() { 9 | return { 10 | // this directive can be used as an Element or an Attribute 11 | restrict: 'EA', 12 | scope: { 13 | // setting config attribute to isolated scope 14 | // config object is 1:1 configuration C3.js object, for avaiable options see: http://c3js.org/examples.html 15 | config: '=' 16 | }, 17 | template: '
', 18 | replace: true, 19 | controller: function($scope, $element) { 20 | 21 | var options = { 22 | element: $element[0].id, 23 | name: false, 24 | value: 25, 25 | image: false, 26 | icon: false, 27 | text: false, 28 | textColor: '#000000', 29 | arcColor: '#57E0EA', 30 | bgArcColor: '#000', 31 | opacity: false, 32 | duration: 1600, 33 | easing: 'bounce' // Raphael easing effect. Don't use backIn or Elastic, they mess up animation :/ 34 | }; 35 | 36 | // merging default options with user options 37 | options = $.extend(options, $scope.config); 38 | 39 | $('#' + options.element).html(''); 40 | 41 | // radius is caluculated from element's width 42 | var radius = $('#' + options.element).width(); 43 | 44 | // new Raphael canvas 45 | var paper = new Raphael(options.element, radius, radius); 46 | 47 | // Make the SVG canvas fill its container - both initially and after resizing 48 | $('#' + options.element + ' svg').css({ height: '100%', width: '100%'}); 49 | 50 | // setting canvas scaling on element resize 51 | paper.setViewBox(0, 0, radius, radius, true ); 52 | paper.canvas.setAttribute('preserveAspectRatio', 'none'); 53 | 54 | // custom arc attribute for easy arc drawing :) 55 | paper.customAttributes.arc = function (xloc, yloc, value, total, R) { 56 | var alpha = 360 / total * value, 57 | a = (90 - alpha) * Math.PI / 180, 58 | x = xloc + R * Math.cos(a), 59 | y = yloc - R * Math.sin(a), 60 | path; 61 | if (total === value) { 62 | path = [ 63 | ["M", xloc, yloc - R], 64 | ["A", R, R, 0, 1, 1, xloc - 0.01, yloc - R] 65 | ]; 66 | } else { 67 | path = [ 68 | ["M", xloc, yloc - R], 69 | ["A", R, R, 0, +(alpha > 180), 1, x, y] 70 | ]; 71 | } 72 | return { 73 | path: path 74 | }; 75 | }; 76 | 77 | // counter function for timely showing percentage counter upto required value 78 | // el - jQuery element 79 | // n - start value 80 | // max - end value 81 | var counter = function (el, n, max) { 82 | (function loop() { 83 | el.html(n + '%'); 84 | if (n++ < max) { 85 | setTimeout(loop, options.duration / max); 86 | } 87 | })(); 88 | }; 89 | 90 | // function showing text beneath gauge and handling it's resizing 91 | var gaugeText = function(element, name, value) 92 | { 93 | // main text 94 | $('#' + element).html(name); 95 | 96 | // percentage 97 | $('#' + element).prepend('
'); 98 | 99 | $('#' + element).css('font-size', $('#' + element).width() / 10 + 'px'); 100 | $('#' + element + 'Percentage').css('font-size', $('#' + element).width() / 4 + 'px'); 101 | 102 | $(window).resize(function() { 103 | $('#' + element).css('font-size', $('#' + element).width() / 10 + 'px'); 104 | $('#' + element + 'Percentage').css('font-size', $('#' + element).width() / 4 + 'px'); 105 | }); 106 | 107 | counter($('#' + element + 'Percentage'), 0, options.value); 108 | }; 109 | 110 | // new image - gauge's background (if it is set) 111 | if (options.image) { 112 | var image = paper.image(options.image, 0, 0, radius, radius); 113 | } 114 | 115 | // adding text in the middle (if it is set) 116 | if (options.text) { 117 | var text = paper.text(radius / 2, radius / 2, options.text) 118 | .attr({ 119 | 'font-size': radius / 16, 120 | "stroke": options.textColor, 121 | "fill": options.textColor 122 | }); 123 | } 124 | 125 | var newArc = false,newArcBg = false; 126 | 127 | $scope.$watch('config.value', function() { 128 | 129 | options.value = $scope.config.value; 130 | 131 | if (newArc) newArc.remove(); 132 | if (newArcBg) newArcBg.remove(); 133 | 134 | // background arc 135 | newArcBg = paper.path().attr({ 136 | "stroke-opacity": (options.opacity) ? options.opacity : "1", 137 | "stroke": options.bgArcColor, 138 | "stroke-width": radius * 0.1, 139 | arc: [radius / 2, radius / 2, 100, 100, radius * 0.425] 140 | }); 141 | 142 | // new arc 143 | newArc = paper.path().attr({ 144 | "stroke-opacity": (options.opacity) ? options.opacity : "1", 145 | "stroke": options.arcColor, 146 | "stroke-width": radius * 0.15, 147 | arc: [radius / 2, radius / 2, 0, 100, radius * 0.425] 148 | }); 149 | 150 | // hover effect (if it is enabled in options) 151 | newArc.hover( 152 | function(event) { 153 | if (options.opacity) { 154 | newArc.animate({ "stroke-opacity": "1" }, 200); 155 | } 156 | }, 157 | function(event) { 158 | if (options.opacity) { 159 | newArc.animate({ "stroke-opacity": options.opacity }, 200); 160 | } 161 | } 162 | ); 163 | 164 | // rotating new arc 165 | newArc.rotate(0, 100 ,100).animate({ 166 | arc: [radius / 2, radius / 2, options.value, 100, radius * 0.425] 167 | }, options.duration, options.easing); 168 | 169 | // adding text under gauge 170 | if (options.name) { 171 | var textName = options.element + 'Text'; 172 | $('#' + options.element).append('
'); 173 | gaugeText(textName, options.name, options.value); 174 | } 175 | 176 | }); 177 | 178 | } 179 | }; 180 | }); 181 | }(Raphael)); 182 | --------------------------------------------------------------------------------