├── .bowerrc
├── .editorconfig
├── .gitattributes
├── .gitignore
├── .jshintrc
├── .travis.yml
├── Gruntfile.js
├── LICENSE
├── README.md
├── app.js
├── app
├── .buildignore
├── .htaccess
├── 404.html
├── favicon.ico
├── images
│ ├── glyphicons-halflings-white.png
│ └── glyphicons-halflings.png
├── index.html
├── robots.txt
├── scripts
│ ├── app.js
│ ├── controllers
│ │ └── main.js
│ ├── directives
│ │ ├── gauge.js
│ │ └── lineChart.js
│ ├── services
│ │ └── websocket.js
│ └── vendor
│ │ └── gauge.js
├── styles
│ ├── bootstrap.css
│ └── main.css
└── views
│ └── main.html
├── bower.json
├── doc
└── realtimecharts.png
├── karma-e2e.conf.js
├── karma.conf.js
├── package.json
└── test
├── .jshintrc
├── runner.html
└── spec
├── controllers
└── main.js
├── directives
├── gauge.js
└── lineChart.js
└── services
└── websocket.js
/.bowerrc:
--------------------------------------------------------------------------------
1 | {
2 | "directory": "app/bower_components"
3 | }
4 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig helps developers define and maintain consistent
2 | # coding styles between different editors and IDEs
3 | # editorconfig.org
4 |
5 | root = true
6 |
7 |
8 | [*]
9 |
10 | # Change these settings to your own preference
11 | indent_style = space
12 | indent_size = 2
13 |
14 | # We recommend you to keep these unchanged
15 | end_of_line = lf
16 | charset = utf-8
17 | trim_trailing_whitespace = true
18 | insert_final_newline = true
19 |
20 | [*.md]
21 | trim_trailing_whitespace = false
22 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | dist
3 | .tmp
4 | .sass-cache
5 | .idea
6 | .DS_Store
7 | app/bower_components
8 |
--------------------------------------------------------------------------------
/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "node": true,
3 | "browser": true,
4 | "esnext": true,
5 | "bitwise": true,
6 | "camelcase": true,
7 | "curly": true,
8 | "eqeqeq": true,
9 | "immed": true,
10 | "indent": 2,
11 | "latedef": true,
12 | "newcap": true,
13 | "noarg": true,
14 | "quotmark": "single",
15 | "regexp": true,
16 | "undef": true,
17 | "unused": true,
18 | "strict": true,
19 | "trailing": true,
20 | "smarttabs": true,
21 | "globals": {
22 | "jQuery": false,
23 | "angular": false,
24 | "google": false,
25 | "Gauge": false
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - '0.10'
4 | before_script:
5 | - 'npm install -g bower grunt-cli'
6 | - 'bower install'
7 |
--------------------------------------------------------------------------------
/Gruntfile.js:
--------------------------------------------------------------------------------
1 | // Generated on 2013-10-27 using generator-angular 0.5.1
2 | 'use strict';
3 |
4 | // # Globbing
5 | // for performance reasons we're only matching one level down:
6 | // 'test/spec/{,*/}*.js'
7 | // use this if you want to recursively match all subfolders:
8 | // 'test/spec/**/*.js'
9 |
10 | module.exports = function (grunt) {
11 | require('load-grunt-tasks')(grunt);
12 | require('time-grunt')(grunt);
13 |
14 | grunt.initConfig({
15 | yeoman: {
16 | // configurable paths
17 | app: require('./bower.json').appPath || 'app',
18 | dist: 'dist'
19 | },
20 | watch: {
21 | coffee: {
22 | files: ['<%= yeoman.app %>/scripts/{,*/}*.coffee'],
23 | tasks: ['coffee:dist']
24 | },
25 | coffeeTest: {
26 | files: ['test/spec/{,*/}*.coffee'],
27 | tasks: ['coffee:test']
28 | },
29 | styles: {
30 | files: ['<%= yeoman.app %>/styles/{,*/}*.css'],
31 | tasks: ['copy:styles', 'autoprefixer']
32 | },
33 | livereload: {
34 | options: {
35 | livereload: '<%= connect.options.livereload %>'
36 | },
37 | files: [
38 | '<%= yeoman.app %>/{,*/}*.html',
39 | '.tmp/styles/{,*/}*.css',
40 | '{.tmp,<%= yeoman.app %>}/scripts/{,*/}*.js',
41 | '<%= yeoman.app %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}'
42 | ]
43 | }
44 | },
45 | autoprefixer: {
46 | options: ['last 1 version'],
47 | dist: {
48 | files: [{
49 | expand: true,
50 | cwd: '.tmp/styles/',
51 | src: '{,*/}*.css',
52 | dest: '.tmp/styles/'
53 | }]
54 | }
55 | },
56 | connect: {
57 | options: {
58 | port: 9000,
59 | // Change this to '0.0.0.0' to access the server from outside.
60 | hostname: 'localhost',
61 | livereload: 35729
62 | },
63 | livereload: {
64 | options: {
65 | open: true,
66 | base: [
67 | '.tmp',
68 | '<%= yeoman.app %>'
69 | ]
70 | }
71 | },
72 | test: {
73 | options: {
74 | port: 9001,
75 | base: [
76 | '.tmp',
77 | 'test',
78 | '<%= yeoman.app %>'
79 | ]
80 | }
81 | },
82 | dist: {
83 | options: {
84 | base: '<%= yeoman.dist %>'
85 | }
86 | }
87 | },
88 | clean: {
89 | dist: {
90 | files: [{
91 | dot: true,
92 | src: [
93 | '.tmp',
94 | '<%= yeoman.dist %>/*',
95 | '!<%= yeoman.dist %>/.git*'
96 | ]
97 | }]
98 | },
99 | server: '.tmp'
100 | },
101 | jshint: {
102 | options: {
103 | jshintrc: '.jshintrc',
104 | ignores: ['<%= yeoman.app %>/scripts/vendor/gauge.js']
105 | },
106 | all: [
107 | 'Gruntfile.js',
108 | '<%= yeoman.app %>/scripts/{,*/}*.js'
109 | ]
110 | },
111 | coffee: {
112 | options: {
113 | sourceMap: true,
114 | sourceRoot: ''
115 | },
116 | dist: {
117 | files: [{
118 | expand: true,
119 | cwd: '<%= yeoman.app %>/scripts',
120 | src: '{,*/}*.coffee',
121 | dest: '.tmp/scripts',
122 | ext: '.js'
123 | }]
124 | },
125 | test: {
126 | files: [{
127 | expand: true,
128 | cwd: 'test/spec',
129 | src: '{,*/}*.coffee',
130 | dest: '.tmp/spec',
131 | ext: '.js'
132 | }]
133 | }
134 | },
135 | // not used since Uglify task does concat,
136 | // but still available if needed
137 | /*concat: {
138 | dist: {}
139 | },*/
140 | rev: {
141 | dist: {
142 | files: {
143 | src: [
144 | '<%= yeoman.dist %>/scripts/{,*/}*.js',
145 | '<%= yeoman.dist %>/styles/{,*/}*.css',
146 | '<%= yeoman.dist %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}',
147 | '<%= yeoman.dist %>/styles/fonts/*'
148 | ]
149 | }
150 | }
151 | },
152 | useminPrepare: {
153 | html: '<%= yeoman.app %>/index.html',
154 | options: {
155 | dest: '<%= yeoman.dist %>'
156 | }
157 | },
158 | usemin: {
159 | html: ['<%= yeoman.dist %>/{,*/}*.html'],
160 | css: ['<%= yeoman.dist %>/styles/{,*/}*.css'],
161 | options: {
162 | dirs: ['<%= yeoman.dist %>']
163 | }
164 | },
165 | imagemin: {
166 | dist: {
167 | files: [{
168 | expand: true,
169 | cwd: '<%= yeoman.app %>/images',
170 | src: '{,*/}*.{png,jpg,jpeg}',
171 | dest: '<%= yeoman.dist %>/images'
172 | }]
173 | }
174 | },
175 | svgmin: {
176 | dist: {
177 | files: [{
178 | expand: true,
179 | cwd: '<%= yeoman.app %>/images',
180 | src: '{,*/}*.svg',
181 | dest: '<%= yeoman.dist %>/images'
182 | }]
183 | }
184 | },
185 | cssmin: {
186 | // By default, your `index.html` will take care of
187 | // minification. This option is pre-configured if you do not wish to use
188 | // Usemin blocks.
189 | // dist: {
190 | // files: {
191 | // '<%= yeoman.dist %>/styles/main.css': [
192 | // '.tmp/styles/{,*/}*.css',
193 | // '<%= yeoman.app %>/styles/{,*/}*.css'
194 | // ]
195 | // }
196 | // }
197 | },
198 | htmlmin: {
199 | dist: {
200 | options: {
201 | /*removeCommentsFromCDATA: true,
202 | // https://github.com/yeoman/grunt-usemin/issues/44
203 | //collapseWhitespace: true,
204 | collapseBooleanAttributes: true,
205 | removeAttributeQuotes: true,
206 | removeRedundantAttributes: true,
207 | useShortDoctype: true,
208 | removeEmptyAttributes: true,
209 | removeOptionalTags: true*/
210 | },
211 | files: [{
212 | expand: true,
213 | cwd: '<%= yeoman.app %>',
214 | src: ['*.html', 'views/*.html'],
215 | dest: '<%= yeoman.dist %>'
216 | }]
217 | }
218 | },
219 | // Put files not handled in other tasks here
220 | copy: {
221 | dist: {
222 | files: [{
223 | expand: true,
224 | dot: true,
225 | cwd: '<%= yeoman.app %>',
226 | dest: '<%= yeoman.dist %>',
227 | src: [
228 | '*.{ico,png,txt}',
229 | '.htaccess',
230 | 'bower_components/**/*',
231 | 'images/{,*/}*.{gif,webp}',
232 | 'styles/fonts/*'
233 | ]
234 | }, {
235 | expand: true,
236 | cwd: '.tmp/images',
237 | dest: '<%= yeoman.dist %>/images',
238 | src: [
239 | 'generated/*'
240 | ]
241 | }]
242 | },
243 | styles: {
244 | expand: true,
245 | cwd: '<%= yeoman.app %>/styles',
246 | dest: '.tmp/styles/',
247 | src: ['{,*/}*.css', '../bower_components/**/*.css']
248 | }
249 | },
250 | concurrent: {
251 | server: [
252 | 'coffee:dist',
253 | 'copy:styles'
254 | ],
255 | test: [
256 | 'coffee',
257 | 'copy:styles'
258 | ],
259 | dist: [
260 | 'coffee',
261 | 'copy:styles',
262 | 'imagemin',
263 | 'svgmin',
264 | 'htmlmin'
265 | ]
266 | },
267 | karma: {
268 | unit: {
269 | configFile: 'karma.conf.js',
270 | singleRun: true
271 | }
272 | },
273 | cdnify: {
274 | dist: {
275 | html: ['<%= yeoman.dist %>/*.html']
276 | }
277 | },
278 | ngmin: {
279 | dist: {
280 | files: [{
281 | expand: true,
282 | cwd: '<%= yeoman.dist %>/scripts',
283 | src: '*.js',
284 | dest: '<%= yeoman.dist %>/scripts'
285 | }]
286 | }
287 | },
288 | uglify: {
289 | dist: {
290 | files: {
291 | '<%= yeoman.dist %>/scripts/scripts.js': [
292 | '<%= yeoman.dist %>/scripts/scripts.js'
293 | ]
294 | }
295 | }
296 | }
297 | });
298 |
299 | grunt.registerTask('server', function (target) {
300 | if (target === 'dist') {
301 | return grunt.task.run(['build', 'connect:dist:keepalive']);
302 | }
303 |
304 | grunt.task.run([
305 | 'clean:server',
306 | 'concurrent:server',
307 | 'autoprefixer',
308 | 'connect:livereload',
309 | 'watch'
310 | ]);
311 | });
312 |
313 | grunt.registerTask('test', [
314 | 'clean:server',
315 | 'concurrent:test',
316 | 'autoprefixer',
317 | 'connect:test',
318 | 'karma'
319 | ]);
320 |
321 | grunt.registerTask('build', [
322 | 'clean:dist',
323 | 'useminPrepare',
324 | 'concurrent:dist',
325 | 'autoprefixer',
326 | 'concat',
327 | 'copy:dist',
328 | 'cdnify',
329 | 'ngmin',
330 | 'cssmin',
331 | 'uglify',
332 | 'rev',
333 | 'usemin'
334 | ]);
335 |
336 | grunt.registerTask('default', [
337 | 'jshint',
338 | 'test',
339 | 'build'
340 | ]);
341 | };
342 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2013
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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | angular-real-time-charts
2 | ========================
3 |
4 | Real Time Charts with AngularJS.
5 |
6 | 
7 |
8 | ## AngularJS directives
9 |
10 | Line Chart with Google Charts
11 |
12 | Gauge with D3.js
13 |
14 | ## Usage
15 |
16 | Install dependencies:
17 |
18 | $ npm install
19 |
20 | Install Bower dependencies:
21 |
22 | $ bower install
23 |
24 | Start Node.js server:
25 |
26 | $ node app
27 |
28 | Application will be available at http://localhost:3000
29 |
30 | ## Links
31 |
32 | [AngularJS](http://angularjs.org/) JavaScript MVC Framework
33 |
34 | [D3](https://github.com/mbostock/d3) JavaScript Data Visualization library
35 |
36 | [Google Charts](https://developers.google.com/chart/) Charts by Google
37 |
38 | [sockjs-node](https://github.com/sockjs/sockjs-node) Node.js WebSocket server
39 |
40 | [sockjs-client](https://github.com/sockjs/sockjs-client) SockJS client
41 |
--------------------------------------------------------------------------------
/app.js:
--------------------------------------------------------------------------------
1 | var express = require('express');
2 | var sockjs = require('sockjs');
3 | var http = require('http');
4 |
5 | var app = express();
6 |
7 | // all environments
8 | app.set('port', process.env.PORT || 3000);
9 | app.use(express.favicon());
10 | app.use(express.logger('dev'));
11 | app.use(express.bodyParser());
12 | app.use(express.methodOverride());
13 | app.use(app.router);
14 |
15 | console.log('environment: ' + app.get('env'));
16 |
17 | if ('production' == app.get('env')) {
18 | app.use(express.static(__dirname + '/dist'));
19 | } else if ('development' == app.get('env')) {
20 | app.use(express.static(__dirname + '/app'));
21 | app.use(express.errorHandler());
22 | }
23 |
24 | var clients = {};
25 | var clientCount = 0;
26 | var interval;
27 |
28 | var gaugeValue = 50;
29 |
30 | function broadcast() {
31 | gaugeValue += Math.random() * 40 - 20;
32 | gaugeValue = gaugeValue < 0 ? 0 : gaugeValue > 100 ? 100 : gaugeValue;
33 | var time = Date.now();
34 |
35 | var message = JSON.stringify({ value: Math.floor(gaugeValue), timestamp: time });
36 |
37 | for (var key in clients) {
38 | if(clients.hasOwnProperty(key)) {
39 | clients[key].write(message);
40 | }
41 | }
42 |
43 | //setTimeout(broadcast, 1000);
44 | }
45 |
46 | function startBroadcast () {
47 | interval = setInterval(broadcast, 1000);
48 | //broadcast();
49 | }
50 |
51 | var sockjsServer = sockjs.createServer();
52 |
53 | sockjsServer.on('connection', function(conn) {
54 | clientCount++;
55 | if (clientCount === 1) {
56 | startBroadcast();
57 | }
58 |
59 | clients[conn.id] = conn;
60 |
61 | conn.on('close', function() {
62 | clientCount--;
63 | delete clients[conn.id];
64 | if (clientCount === 0) {
65 | clearInterval(interval);
66 | }
67 | });
68 | });
69 |
70 | var server = http.createServer(app).listen(app.get('port'), function(){
71 | console.log('Express server listening on port ' + app.get('port'));
72 | });
73 |
74 | sockjsServer.installHandlers(server, { prefix: '/sockjs' });
--------------------------------------------------------------------------------
/app/.buildignore:
--------------------------------------------------------------------------------
1 | *.coffee
--------------------------------------------------------------------------------
/app/.htaccess:
--------------------------------------------------------------------------------
1 | # Apache Configuration File
2 |
3 | # (!) Using `.htaccess` files slows down Apache, therefore, if you have access
4 | # to the main server config file (usually called `httpd.conf`), you should add
5 | # this logic there: http://httpd.apache.org/docs/current/howto/htaccess.html.
6 |
7 | # ##############################################################################
8 | # # CROSS-ORIGIN RESOURCE SHARING (CORS) #
9 | # ##############################################################################
10 |
11 | # ------------------------------------------------------------------------------
12 | # | Cross-domain AJAX requests |
13 | # ------------------------------------------------------------------------------
14 |
15 | # Enable cross-origin AJAX requests.
16 | # http://code.google.com/p/html5security/wiki/CrossOriginRequestSecurity
17 | # http://enable-cors.org/
18 |
19 | #
20 | # Header set Access-Control-Allow-Origin "*"
21 | #
22 |
23 | # ------------------------------------------------------------------------------
24 | # | CORS-enabled images |
25 | # ------------------------------------------------------------------------------
26 |
27 | # Send the CORS header for images when browsers request it.
28 | # https://developer.mozilla.org/en/CORS_Enabled_Image
29 | # http://blog.chromium.org/2011/07/using-cross-domain-images-in-webgl-and.html
30 | # http://hacks.mozilla.org/2011/11/using-cors-to-load-webgl-textures-from-cross-domain-images/
31 |
32 |
33 |
34 |
35 | SetEnvIf Origin ":" IS_CORS
36 | Header set Access-Control-Allow-Origin "*" env=IS_CORS
37 |
38 |
39 |
40 |
41 | # ------------------------------------------------------------------------------
42 | # | Web fonts access |
43 | # ------------------------------------------------------------------------------
44 |
45 | # Allow access from all domains for web fonts
46 |
47 |
48 |
49 | Header set Access-Control-Allow-Origin "*"
50 |
51 |
52 |
53 |
54 | # ##############################################################################
55 | # # ERRORS #
56 | # ##############################################################################
57 |
58 | # ------------------------------------------------------------------------------
59 | # | 404 error prevention for non-existing redirected folders |
60 | # ------------------------------------------------------------------------------
61 |
62 | # Prevent Apache from returning a 404 error for a rewrite if a directory
63 | # with the same name does not exist.
64 | # http://httpd.apache.org/docs/current/content-negotiation.html#multiviews
65 | # http://www.webmasterworld.com/apache/3808792.htm
66 |
67 | Options -MultiViews
68 |
69 | # ------------------------------------------------------------------------------
70 | # | Custom error messages / pages |
71 | # ------------------------------------------------------------------------------
72 |
73 | # You can customize what Apache returns to the client in case of an error (see
74 | # http://httpd.apache.org/docs/current/mod/core.html#errordocument), e.g.:
75 |
76 | ErrorDocument 404 /404.html
77 |
78 |
79 | # ##############################################################################
80 | # # INTERNET EXPLORER #
81 | # ##############################################################################
82 |
83 | # ------------------------------------------------------------------------------
84 | # | Better website experience |
85 | # ------------------------------------------------------------------------------
86 |
87 | # Force IE to render pages in the highest available mode in the various
88 | # cases when it may not: http://hsivonen.iki.fi/doctype/ie-mode.pdf.
89 |
90 |
91 | Header set X-UA-Compatible "IE=edge"
92 | # `mod_headers` can't match based on the content-type, however, we only
93 | # want to send this header for HTML pages and not for the other resources
94 |
95 | Header unset X-UA-Compatible
96 |
97 |
98 |
99 | # ------------------------------------------------------------------------------
100 | # | Cookie setting from iframes |
101 | # ------------------------------------------------------------------------------
102 |
103 | # Allow cookies to be set from iframes in IE.
104 |
105 | #
106 | # Header set P3P "policyref=\"/w3c/p3p.xml\", CP=\"IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT\""
107 | #
108 |
109 | # ------------------------------------------------------------------------------
110 | # | Screen flicker |
111 | # ------------------------------------------------------------------------------
112 |
113 | # Stop screen flicker in IE on CSS rollovers (this only works in
114 | # combination with the `ExpiresByType` directives for images from below).
115 |
116 | # BrowserMatch "MSIE" brokenvary=1
117 | # BrowserMatch "Mozilla/4.[0-9]{2}" brokenvary=1
118 | # BrowserMatch "Opera" !brokenvary
119 | # SetEnvIf brokenvary 1 force-no-vary
120 |
121 |
122 | # ##############################################################################
123 | # # MIME TYPES AND ENCODING #
124 | # ##############################################################################
125 |
126 | # ------------------------------------------------------------------------------
127 | # | Proper MIME types for all files |
128 | # ------------------------------------------------------------------------------
129 |
130 |
131 |
132 | # Audio
133 | AddType audio/mp4 m4a f4a f4b
134 | AddType audio/ogg oga ogg
135 |
136 | # JavaScript
137 | # Normalize to standard type (it's sniffed in IE anyways):
138 | # http://tools.ietf.org/html/rfc4329#section-7.2
139 | AddType application/javascript js jsonp
140 | AddType application/json json
141 |
142 | # Video
143 | AddType video/mp4 mp4 m4v f4v f4p
144 | AddType video/ogg ogv
145 | AddType video/webm webm
146 | AddType video/x-flv flv
147 |
148 | # Web fonts
149 | AddType application/font-woff woff
150 | AddType application/vnd.ms-fontobject eot
151 |
152 | # Browsers usually ignore the font MIME types and sniff the content,
153 | # however, Chrome shows a warning if other MIME types are used for the
154 | # following fonts.
155 | AddType application/x-font-ttf ttc ttf
156 | AddType font/opentype otf
157 |
158 | # Make SVGZ fonts work on iPad:
159 | # https://twitter.com/FontSquirrel/status/14855840545
160 | AddType image/svg+xml svg svgz
161 | AddEncoding gzip svgz
162 |
163 | # Other
164 | AddType application/octet-stream safariextz
165 | AddType application/x-chrome-extension crx
166 | AddType application/x-opera-extension oex
167 | AddType application/x-shockwave-flash swf
168 | AddType application/x-web-app-manifest+json webapp
169 | AddType application/x-xpinstall xpi
170 | AddType application/xml atom rdf rss xml
171 | AddType image/webp webp
172 | AddType image/x-icon ico
173 | AddType text/cache-manifest appcache manifest
174 | AddType text/vtt vtt
175 | AddType text/x-component htc
176 | AddType text/x-vcard vcf
177 |
178 |
179 |
180 | # ------------------------------------------------------------------------------
181 | # | UTF-8 encoding |
182 | # ------------------------------------------------------------------------------
183 |
184 | # Use UTF-8 encoding for anything served as `text/html` or `text/plain`.
185 | AddDefaultCharset utf-8
186 |
187 | # Force UTF-8 for certain file formats.
188 |
189 | AddCharset utf-8 .atom .css .js .json .rss .vtt .webapp .xml
190 |
191 |
192 |
193 | # ##############################################################################
194 | # # URL REWRITES #
195 | # ##############################################################################
196 |
197 | # ------------------------------------------------------------------------------
198 | # | Rewrite engine |
199 | # ------------------------------------------------------------------------------
200 |
201 | # Turning on the rewrite engine and enabling the `FollowSymLinks` option is
202 | # necessary for the following directives to work.
203 |
204 | # If your web host doesn't allow the `FollowSymlinks` option, you may need to
205 | # comment it out and use `Options +SymLinksIfOwnerMatch` but, be aware of the
206 | # performance impact: http://httpd.apache.org/docs/current/misc/perf-tuning.html#symlinks
207 |
208 | # Also, some cloud hosting services require `RewriteBase` to be set:
209 | # http://www.rackspace.com/knowledge_center/frequently-asked-question/why-is-mod-rewrite-not-working-on-my-site
210 |
211 |
212 | Options +FollowSymlinks
213 | # Options +SymLinksIfOwnerMatch
214 | RewriteEngine On
215 | # RewriteBase /
216 |
217 |
218 | # ------------------------------------------------------------------------------
219 | # | Suppressing / Forcing the "www." at the beginning of URLs |
220 | # ------------------------------------------------------------------------------
221 |
222 | # The same content should never be available under two different URLs especially
223 | # not with and without "www." at the beginning. This can cause SEO problems
224 | # (duplicate content), therefore, you should choose one of the alternatives and
225 | # redirect the other one.
226 |
227 | # By default option 1 (no "www.") is activated:
228 | # http://no-www.org/faq.php?q=class_b
229 |
230 | # If you'd prefer to use option 2, just comment out all the lines from option 1
231 | # and uncomment the ones from option 2.
232 |
233 | # IMPORTANT: NEVER USE BOTH RULES AT THE SAME TIME!
234 |
235 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
236 |
237 | # Option 1: rewrite www.example.com → example.com
238 |
239 |
240 | RewriteCond %{HTTPS} !=on
241 | RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC]
242 | RewriteRule ^ http://%1%{REQUEST_URI} [R=301,L]
243 |
244 |
245 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
246 |
247 | # Option 2: rewrite example.com → www.example.com
248 |
249 | # Be aware that the following might not be a good idea if you use "real"
250 | # subdomains for certain parts of your website.
251 |
252 | #
253 | # RewriteCond %{HTTPS} !=on
254 | # RewriteCond %{HTTP_HOST} !^www\..+$ [NC]
255 | # RewriteRule ^ http://www.%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
256 | #
257 |
258 |
259 | # ##############################################################################
260 | # # SECURITY #
261 | # ##############################################################################
262 |
263 | # ------------------------------------------------------------------------------
264 | # | Content Security Policy (CSP) |
265 | # ------------------------------------------------------------------------------
266 |
267 | # You can mitigate the risk of cross-site scripting and other content-injection
268 | # attacks by setting a Content Security Policy which whitelists trusted sources
269 | # of content for your site.
270 |
271 | # The example header below allows ONLY scripts that are loaded from the current
272 | # site's origin (no inline scripts, no CDN, etc). This almost certainly won't
273 | # work as-is for your site!
274 |
275 | # To get all the details you'll need to craft a reasonable policy for your site,
276 | # read: http://html5rocks.com/en/tutorials/security/content-security-policy (or
277 | # see the specification: http://w3.org/TR/CSP).
278 |
279 | #
280 | # Header set Content-Security-Policy "script-src 'self'; object-src 'self'"
281 | #
282 | # Header unset Content-Security-Policy
283 | #
284 | #
285 |
286 | # ------------------------------------------------------------------------------
287 | # | File access |
288 | # ------------------------------------------------------------------------------
289 |
290 | # Block access to directories without a default document.
291 | # Usually you should leave this uncommented because you shouldn't allow anyone
292 | # to surf through every directory on your server (which may includes rather
293 | # private places like the CMS's directories).
294 |
295 |
296 | Options -Indexes
297 |
298 |
299 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
300 |
301 | # Block access to hidden files and directories.
302 | # This includes directories used by version control systems such as Git and SVN.
303 |
304 |
305 | RewriteCond %{SCRIPT_FILENAME} -d [OR]
306 | RewriteCond %{SCRIPT_FILENAME} -f
307 | RewriteRule "(^|/)\." - [F]
308 |
309 |
310 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
311 |
312 | # Block access to backup and source files.
313 | # These files may be left by some text editors and can pose a great security
314 | # danger when anyone has access to them.
315 |
316 |
317 | Order allow,deny
318 | Deny from all
319 | Satisfy All
320 |
321 |
322 | # ------------------------------------------------------------------------------
323 | # | Secure Sockets Layer (SSL) |
324 | # ------------------------------------------------------------------------------
325 |
326 | # Rewrite secure requests properly to prevent SSL certificate warnings, e.g.:
327 | # prevent `https://www.example.com` when your certificate only allows
328 | # `https://secure.example.com`.
329 |
330 | #
331 | # RewriteCond %{SERVER_PORT} !^443
332 | # RewriteRule ^ https://example-domain-please-change-me.com%{REQUEST_URI} [R=301,L]
333 | #
334 |
335 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
336 |
337 | # Force client-side SSL redirection.
338 |
339 | # If a user types "example.com" in his browser, the above rule will redirect him
340 | # to the secure version of the site. That still leaves a window of opportunity
341 | # (the initial HTTP connection) for an attacker to downgrade or redirect the
342 | # request. The following header ensures that browser will ONLY connect to your
343 | # server via HTTPS, regardless of what the users type in the address bar.
344 | # http://www.html5rocks.com/en/tutorials/security/transport-layer-security/
345 |
346 | #
347 | # Header set Strict-Transport-Security max-age=16070400;
348 | #
349 |
350 | # ------------------------------------------------------------------------------
351 | # | Server software information |
352 | # ------------------------------------------------------------------------------
353 |
354 | # Avoid displaying the exact Apache version number, the description of the
355 | # generic OS-type and the information about Apache's compiled-in modules.
356 |
357 | # ADD THIS DIRECTIVE IN THE `httpd.conf` AS IT WILL NOT WORK IN THE `.htaccess`!
358 |
359 | # ServerTokens Prod
360 |
361 |
362 | # ##############################################################################
363 | # # WEB PERFORMANCE #
364 | # ##############################################################################
365 |
366 | # ------------------------------------------------------------------------------
367 | # | Compression |
368 | # ------------------------------------------------------------------------------
369 |
370 |
371 |
372 | # Force compression for mangled headers.
373 | # http://developer.yahoo.com/blogs/ydn/posts/2010/12/pushing-beyond-gzipping
374 |
375 |
376 | SetEnvIfNoCase ^(Accept-EncodXng|X-cept-Encoding|X{15}|~{15}|-{15})$ ^((gzip|deflate)\s*,?\s*)+|[X~-]{4,13}$ HAVE_Accept-Encoding
377 | RequestHeader append Accept-Encoding "gzip,deflate" env=HAVE_Accept-Encoding
378 |
379 |
380 |
381 | # Compress all output labeled with one of the following MIME-types
382 | # (for Apache versions below 2.3.7, you don't need to enable `mod_filter`
383 | # and can remove the `` and `` lines
384 | # as `AddOutputFilterByType` is still in the core directives).
385 |
386 | AddOutputFilterByType DEFLATE application/atom+xml \
387 | application/javascript \
388 | application/json \
389 | application/rss+xml \
390 | application/vnd.ms-fontobject \
391 | application/x-font-ttf \
392 | application/x-web-app-manifest+json \
393 | application/xhtml+xml \
394 | application/xml \
395 | font/opentype \
396 | image/svg+xml \
397 | image/x-icon \
398 | text/css \
399 | text/html \
400 | text/plain \
401 | text/x-component \
402 | text/xml
403 |
404 |
405 |
406 |
407 | # ------------------------------------------------------------------------------
408 | # | Content transformations |
409 | # ------------------------------------------------------------------------------
410 |
411 | # Prevent some of the mobile network providers from modifying the content of
412 | # your site: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9.5.
413 |
414 | #
415 | # Header set Cache-Control "no-transform"
416 | #
417 |
418 | # ------------------------------------------------------------------------------
419 | # | ETag removal |
420 | # ------------------------------------------------------------------------------
421 |
422 | # Since we're sending far-future expires headers (see below), ETags can
423 | # be removed: http://developer.yahoo.com/performance/rules.html#etags.
424 |
425 | # `FileETag None` is not enough for every server.
426 |
427 | Header unset ETag
428 |
429 |
430 | FileETag None
431 |
432 | # ------------------------------------------------------------------------------
433 | # | Expires headers (for better cache control) |
434 | # ------------------------------------------------------------------------------
435 |
436 | # The following expires headers are set pretty far in the future. If you don't
437 | # control versioning with filename-based cache busting, consider lowering the
438 | # cache time for resources like CSS and JS to something like 1 week.
439 |
440 |
441 |
442 | ExpiresActive on
443 | ExpiresDefault "access plus 1 month"
444 |
445 | # CSS
446 | ExpiresByType text/css "access plus 1 year"
447 |
448 | # Data interchange
449 | ExpiresByType application/json "access plus 0 seconds"
450 | ExpiresByType application/xml "access plus 0 seconds"
451 | ExpiresByType text/xml "access plus 0 seconds"
452 |
453 | # Favicon (cannot be renamed!)
454 | ExpiresByType image/x-icon "access plus 1 week"
455 |
456 | # HTML components (HTCs)
457 | ExpiresByType text/x-component "access plus 1 month"
458 |
459 | # HTML
460 | ExpiresByType text/html "access plus 0 seconds"
461 |
462 | # JavaScript
463 | ExpiresByType application/javascript "access plus 1 year"
464 |
465 | # Manifest files
466 | ExpiresByType application/x-web-app-manifest+json "access plus 0 seconds"
467 | ExpiresByType text/cache-manifest "access plus 0 seconds"
468 |
469 | # Media
470 | ExpiresByType audio/ogg "access plus 1 month"
471 | ExpiresByType image/gif "access plus 1 month"
472 | ExpiresByType image/jpeg "access plus 1 month"
473 | ExpiresByType image/png "access plus 1 month"
474 | ExpiresByType video/mp4 "access plus 1 month"
475 | ExpiresByType video/ogg "access plus 1 month"
476 | ExpiresByType video/webm "access plus 1 month"
477 |
478 | # Web feeds
479 | ExpiresByType application/atom+xml "access plus 1 hour"
480 | ExpiresByType application/rss+xml "access plus 1 hour"
481 |
482 | # Web fonts
483 | ExpiresByType application/font-woff "access plus 1 month"
484 | ExpiresByType application/vnd.ms-fontobject "access plus 1 month"
485 | ExpiresByType application/x-font-ttf "access plus 1 month"
486 | ExpiresByType font/opentype "access plus 1 month"
487 | ExpiresByType image/svg+xml "access plus 1 month"
488 |
489 |
490 |
491 | # ------------------------------------------------------------------------------
492 | # | Filename-based cache busting |
493 | # ------------------------------------------------------------------------------
494 |
495 | # If you're not using a build process to manage your filename version revving,
496 | # you might want to consider enabling the following directives to route all
497 | # requests such as `/css/style.12345.css` to `/css/style.css`.
498 |
499 | # To understand why this is important and a better idea than `*.css?v231`, read:
500 | # http://stevesouders.com/blog/2008/08/23/revving-filenames-dont-use-querystring
501 |
502 | #
503 | # RewriteCond %{REQUEST_FILENAME} !-f
504 | # RewriteCond %{REQUEST_FILENAME} !-d
505 | # RewriteRule ^(.+)\.(\d+)\.(js|css|png|jpg|gif)$ $1.$3 [L]
506 | #
507 |
508 | # ------------------------------------------------------------------------------
509 | # | File concatenation |
510 | # ------------------------------------------------------------------------------
511 |
512 | # Allow concatenation from within specific CSS and JS files, e.g.:
513 | # Inside of `script.combined.js` you could have
514 | #
515 | #
516 | # and they would be included into this single file.
517 |
518 | #
519 | #
520 | # Options +Includes
521 | # AddOutputFilterByType INCLUDES application/javascript application/json
522 | # SetOutputFilter INCLUDES
523 | #
524 | #
525 | # Options +Includes
526 | # AddOutputFilterByType INCLUDES text/css
527 | # SetOutputFilter INCLUDES
528 | #
529 | #
530 |
531 | # ------------------------------------------------------------------------------
532 | # | Persistent connections |
533 | # ------------------------------------------------------------------------------
534 |
535 | # Allow multiple requests to be sent over the same TCP connection:
536 | # http://httpd.apache.org/docs/current/en/mod/core.html#keepalive.
537 |
538 | # Enable if you serve a lot of static content but, be aware of the
539 | # possible disadvantages!
540 |
541 | #
542 | # Header set Connection Keep-Alive
543 | #
544 |
--------------------------------------------------------------------------------
/app/404.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Page Not Found :(
6 |
141 |
142 |
143 |
144 |
Not found :(
145 |
Sorry, but the page you were trying to view does not exist.
146 |
It looks like this was the result of either:
147 |
148 | - a mistyped address
149 | - an out-of-date link
150 |
151 |
154 |
155 |
156 |
157 |
158 |
--------------------------------------------------------------------------------
/app/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nickholub/angular-real-time-charts/e800ea9be8e3802c20808468c02db503a50af3c6/app/favicon.ico
--------------------------------------------------------------------------------
/app/images/glyphicons-halflings-white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nickholub/angular-real-time-charts/e800ea9be8e3802c20808468c02db503a50af3c6/app/images/glyphicons-halflings-white.png
--------------------------------------------------------------------------------
/app/images/glyphicons-halflings.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nickholub/angular-real-time-charts/e800ea9be8e3802c20808468c02db503a50af3c6/app/images/glyphicons-halflings.png
--------------------------------------------------------------------------------
/app/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
27 |
28 |
32 |
33 |
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 |
92 |
93 |
94 |
--------------------------------------------------------------------------------
/app/robots.txt:
--------------------------------------------------------------------------------
1 | # robotstxt.org
2 |
3 | User-agent: *
4 |
--------------------------------------------------------------------------------
/app/scripts/app.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | angular.module('app', ['ngRoute', 'app.service', 'app.directive', 'app.controller']);
4 |
5 | angular.module('app').config(function ($routeProvider, webSocketProvider) {
6 | webSocketProvider.setWebSocketURL('ws://' + window.location.host + '/sockjs/websocket');
7 |
8 | $routeProvider
9 | .when('/', {
10 | templateUrl: 'views/main.html',
11 | controller: 'MainCtrl'
12 | })
13 | .otherwise({
14 | redirectTo: '/'
15 | });
16 | });
17 |
18 |
--------------------------------------------------------------------------------
/app/scripts/controllers/main.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | angular.module('app.controller', []);
4 |
5 | angular.module('app.controller')
6 | .controller('MainCtrl', function ($scope, webSocket) {
7 | $scope.gaugeValue = 0;
8 |
9 | var items = [];
10 |
11 | webSocket.subscribe(function (item) {
12 | items.push(item);
13 |
14 | if (items.length > 40) {
15 | items.shift();
16 | }
17 |
18 | $scope.chart = {
19 | data: items,
20 | max: 30
21 | };
22 |
23 | $scope.gaugeValue = item.value;
24 | $scope.$apply();
25 | });
26 | });
27 |
--------------------------------------------------------------------------------
/app/scripts/directives/gauge.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Modified copy of https://github.com/lithiumtech/angular_and_d3/blob/master/step5/custom/gauges.js
3 | */
4 |
5 | 'use strict';
6 |
7 | angular.module('app.directive', []);
8 |
9 | angular.module('app.directive')
10 | .directive('gauge', function () {
11 | return {
12 | restrict: 'E',
13 | replace: true,
14 | scope: {
15 | label: '@',
16 | min: '=',
17 | max: '=',
18 | value: '='
19 | },
20 | link: function postLink(scope, element, attrs) {
21 | var config = {
22 | size: 250,
23 | label: attrs.label,
24 | min: undefined !== scope.min ? scope.min : 0,
25 | max: undefined !== scope.max ? scope.max : 100,
26 | minorTicks: 5
27 | };
28 |
29 | var range = config.max - config.min;
30 | config.yellowZones = [
31 | { from: config.min + range * 0.75, to: config.min + range * 0.9 }
32 | ];
33 | config.redZones = [
34 | { from: config.min + range * 0.9, to: config.max }
35 | ];
36 |
37 | scope.gauge = new Gauge(element[0], config);
38 | scope.gauge.render();
39 | scope.gauge.redraw(scope.value);
40 |
41 | scope.$watch('value', function () {
42 | if (scope.gauge) {
43 | scope.gauge.redraw(scope.value);
44 | }
45 | });
46 | }
47 | };
48 | });
49 |
--------------------------------------------------------------------------------
/app/scripts/directives/lineChart.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | angular.module('app.directive')
4 | .directive('lineChart', function () {
5 | return {
6 | template: '',
7 | scope: {
8 | chart: '='
9 | },
10 | restrict: 'E',
11 | replace: true,
12 | link: function postLink(scope, element) {
13 | var lineChart = new google.visualization.LineChart(element[0]);
14 |
15 | function draw(chart) {
16 | var data = chart.data;
17 |
18 | var table = new google.visualization.DataTable();
19 | table.addColumn('datetime');
20 | table.addColumn('number');
21 | table.addRows(data.length);
22 |
23 | var view = new google.visualization.DataView(table);
24 |
25 | for (var i = 0; i < data.length; i++) {
26 | var item = data[i];
27 | table.setCell(i, 0, new Date(item.timestamp));
28 | var value = parseFloat(item.value);
29 | table.setCell(i, 1, value);
30 | }
31 |
32 | var last = data[data.length - 1];
33 | var max = new Date(last.timestamp);
34 | var min = new Date(last.timestamp - chart.max * 1000);
35 |
36 | var chartOptions = {
37 | legend: 'none',
38 | vAxis: { minValue: 0, maxValue: 100 },
39 | hAxis: { viewWindow: { min: min, max: max }}
40 | };
41 |
42 | lineChart.draw(view, chartOptions);
43 | }
44 |
45 | scope.$watch('chart', function (chart) {
46 | if (chart && chart.data && chart.max) {
47 | draw(chart);
48 | }
49 | });
50 | }
51 | };
52 | });
53 |
--------------------------------------------------------------------------------
/app/scripts/services/websocket.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | angular.module('app.service', ['ng']);
4 |
5 | angular.module('app.service')
6 | .provider('webSocket', function () {
7 |
8 | var webSocketURL;
9 | var webSocketObject; // for testing only
10 |
11 | return {
12 | $get: function($q) {
13 | if (!webSocketURL && !webSocketObject) {
14 | throw 'WebSocket URL is not defined';
15 | }
16 |
17 | var socket = !webSocketObject ? new WebSocket(webSocketURL) : webSocketObject;
18 |
19 | var deferred = $q.defer();
20 |
21 | socket.onopen = function() {
22 | deferred.resolve();
23 | };
24 |
25 | var callbacks = jQuery.Callbacks();
26 |
27 | socket.onmessage = function(e) {
28 | var data = JSON.parse(e.data);
29 | callbacks.fire(data);
30 | };
31 |
32 | return {
33 | send: function (message) {
34 | var msg = JSON.stringify(message);
35 |
36 | deferred.promise.then(function () {
37 | socket.send(msg);
38 | });
39 | },
40 |
41 | subscribe: function(callback) {
42 | callbacks.add(callback);
43 | }
44 | };
45 | },
46 |
47 | setWebSocketURL: function(wsURL) {
48 | webSocketURL = wsURL;
49 | },
50 |
51 | setWebSocketObject: function(wsObject) {
52 | webSocketObject = wsObject;
53 | }
54 | };
55 | });
56 |
--------------------------------------------------------------------------------
/app/scripts/vendor/gauge.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copied from https://github.com/lithiumtech/angular_and_d3
3 | */
4 |
5 | function Gauge(element, configuration)
6 | {
7 | this.element = element;
8 |
9 | var self = this; // for internal d3 functions
10 |
11 | this.configure = function(configuration)
12 | {
13 | this.config = configuration;
14 |
15 | this.config.size = this.config.size * 0.9;
16 |
17 | this.config.raduis = this.config.size * 0.97 / 2;
18 | this.config.cx = this.config.size / 2;
19 | this.config.cy = this.config.size / 2;
20 |
21 | this.config.min = undefined != configuration.min ? configuration.min : 0;
22 | this.config.max = undefined != configuration.max ? configuration.max : 100;
23 | this.config.range = this.config.max - this.config.min;
24 |
25 | this.config.majorTicks = configuration.majorTicks || 5;
26 | this.config.minorTicks = configuration.minorTicks || 2;
27 |
28 | this.config.greenColor = configuration.greenColor || "#109618";
29 | this.config.yellowColor = configuration.yellowColor || "#FF9900";
30 | this.config.redColor = configuration.redColor || "#DC3912";
31 |
32 | this.config.transitionDuration = configuration.transitionDuration || 500;
33 | }
34 |
35 | this.render = function()
36 | {
37 | this.body = d3.select( this.element )
38 | .append("svg:svg")
39 | .attr("class", "gauge")
40 | .attr("width", this.config.size)
41 | .attr("height", this.config.size);
42 |
43 | this.body.append("svg:circle")
44 | .attr("cx", this.config.cx)
45 | .attr("cy", this.config.cy)
46 | .attr("r", this.config.raduis)
47 | .style("fill", "#ccc")
48 | .style("stroke", "#000")
49 | .style("stroke-width", "0.5px");
50 |
51 | this.body.append("svg:circle")
52 | .attr("cx", this.config.cx)
53 | .attr("cy", this.config.cy)
54 | .attr("r", 0.9 * this.config.raduis)
55 | .style("fill", "#fff")
56 | .style("stroke", "#e0e0e0")
57 | .style("stroke-width", "2px");
58 |
59 | for (var index in this.config.greenZones)
60 | {
61 | this.drawBand(this.config.greenZones[index].from, this.config.greenZones[index].to, self.config.greenColor);
62 | }
63 |
64 | for (var index in this.config.yellowZones)
65 | {
66 | this.drawBand(this.config.yellowZones[index].from, this.config.yellowZones[index].to, self.config.yellowColor);
67 | }
68 |
69 | for (var index in this.config.redZones)
70 | {
71 | this.drawBand(this.config.redZones[index].from, this.config.redZones[index].to, self.config.redColor);
72 | }
73 |
74 | if (undefined != this.config.label)
75 | {
76 | var fontSize = Math.round(this.config.size / 9);
77 | this.body.append("svg:text")
78 | .attr("x", this.config.cx)
79 | .attr("y", this.config.cy / 2 + fontSize / 2)
80 | .attr("dy", fontSize / 2)
81 | .attr("text-anchor", "middle")
82 | .text(this.config.label)
83 | .style("font-size", fontSize + "px")
84 | .style("fill", "#333")
85 | .style("stroke-width", "0px");
86 | }
87 |
88 | var fontSize = Math.round(this.config.size / 16);
89 | var majorDelta = this.config.range / (this.config.majorTicks - 1);
90 | for (var major = this.config.min; major <= this.config.max; major += majorDelta)
91 | {
92 | var minorDelta = majorDelta / this.config.minorTicks;
93 | for (var minor = major + minorDelta; minor < Math.min(major + majorDelta, this.config.max); minor += minorDelta)
94 | {
95 | var point1 = this.valueToPoint(minor, 0.75);
96 | var point2 = this.valueToPoint(minor, 0.85);
97 |
98 | this.body.append("svg:line")
99 | .attr("x1", point1.x)
100 | .attr("y1", point1.y)
101 | .attr("x2", point2.x)
102 | .attr("y2", point2.y)
103 | .style("stroke", "#666")
104 | .style("stroke-width", "1px");
105 | }
106 |
107 | var point1 = this.valueToPoint(major, 0.7);
108 | var point2 = this.valueToPoint(major, 0.85);
109 |
110 | this.body.append("svg:line")
111 | .attr("x1", point1.x)
112 | .attr("y1", point1.y)
113 | .attr("x2", point2.x)
114 | .attr("y2", point2.y)
115 | .style("stroke", "#333")
116 | .style("stroke-width", "2px");
117 |
118 | if (major == this.config.min || major == this.config.max)
119 | {
120 | var point = this.valueToPoint(major, 0.63);
121 |
122 | this.body.append("svg:text")
123 | .attr("x", point.x)
124 | .attr("y", point.y)
125 | .attr("dy", fontSize / 3)
126 | .attr("text-anchor", major == this.config.min ? "start" : "end")
127 | .text(major)
128 | .style("font-size", fontSize + "px")
129 | .style("fill", "#333")
130 | .style("stroke-width", "0px");
131 | }
132 | }
133 |
134 | var pointerContainer = this.body.append("svg:g").attr("class", "pointerContainer");
135 |
136 | var midValue = (this.config.min + this.config.max) / 2;
137 |
138 | var pointerPath = this.buildPointerPath(midValue);
139 |
140 | var pointerLine = d3.svg.line()
141 | .x(function(d) { return d.x })
142 | .y(function(d) { return d.y })
143 | .interpolate("basis");
144 |
145 | pointerContainer.selectAll("path")
146 | .data([pointerPath])
147 | .enter()
148 | .append("svg:path")
149 | .attr("d", pointerLine)
150 | .style("fill", "#dc3912")
151 | .style("stroke", "#c63310")
152 | .style("fill-opacity", 0.7)
153 |
154 | pointerContainer.append("svg:circle")
155 | .attr("cx", this.config.cx)
156 | .attr("cy", this.config.cy)
157 | .attr("r", 0.12 * this.config.raduis)
158 | .style("fill", "#4684EE")
159 | .style("stroke", "#666")
160 | .style("opacity", 1);
161 |
162 | var fontSize = Math.round(this.config.size / 10);
163 | pointerContainer.selectAll("text")
164 | .data([midValue])
165 | .enter()
166 | .append("svg:text")
167 | .attr("x", this.config.cx)
168 | .attr("y", this.config.size - this.config.cy / 4 - fontSize)
169 | .attr("dy", fontSize / 2)
170 | .attr("text-anchor", "middle")
171 | .style("font-size", fontSize + "px")
172 | .style("fill", "#000")
173 | .style("stroke-width", "0px");
174 |
175 | this.redraw(this.config.min, 0);
176 | }
177 |
178 | this.buildPointerPath = function(value)
179 | {
180 | var delta = this.config.range / 13;
181 |
182 | var head = valueToPoint(value, 0.85);
183 | var head1 = valueToPoint(value - delta, 0.12);
184 | var head2 = valueToPoint(value + delta, 0.12);
185 |
186 | var tailValue = value - (this.config.range * (1/(270/360)) / 2);
187 | var tail = valueToPoint(tailValue, 0.28);
188 | var tail1 = valueToPoint(tailValue - delta, 0.12);
189 | var tail2 = valueToPoint(tailValue + delta, 0.12);
190 |
191 | return [head, head1, tail2, tail, tail1, head2, head];
192 |
193 | function valueToPoint(value, factor)
194 | {
195 | var point = self.valueToPoint(value, factor);
196 | point.x -= self.config.cx;
197 | point.y -= self.config.cy;
198 | return point;
199 | }
200 | }
201 |
202 | this.drawBand = function(start, end, color)
203 | {
204 | if (0 >= end - start) return;
205 |
206 | this.body.append("svg:path")
207 | .style("fill", color)
208 | .attr("d", d3.svg.arc()
209 | .startAngle(this.valueToRadians(start))
210 | .endAngle(this.valueToRadians(end))
211 | .innerRadius(0.65 * this.config.raduis)
212 | .outerRadius(0.85 * this.config.raduis))
213 | .attr("transform", function() { return "translate(" + self.config.cx + ", " + self.config.cy + ") rotate(270)" });
214 | }
215 |
216 | this.redraw = function(value, transitionDuration)
217 | {
218 | var pointerContainer = this.body.select(".pointerContainer");
219 |
220 | pointerContainer.selectAll("text").text(Math.round(value));
221 |
222 | var pointer = pointerContainer.selectAll("path");
223 | pointer.transition()
224 | .duration(undefined != transitionDuration ? transitionDuration : this.config.transitionDuration)
225 | //.delay(0)
226 | //.ease("linear")
227 | //.attr("transform", function(d)
228 | .attrTween("transform", function()
229 | {
230 | var pointerValue = value;
231 | if (value > self.config.max) pointerValue = self.config.max + 0.02*self.config.range;
232 | else if (value < self.config.min) pointerValue = self.config.min - 0.02*self.config.range;
233 | var targetRotation = (self.valueToDegrees(pointerValue) - 90);
234 | var currentRotation = self._currentRotation || targetRotation;
235 | self._currentRotation = targetRotation;
236 |
237 | return function(step)
238 | {
239 | var rotation = currentRotation + (targetRotation-currentRotation)*step;
240 | return "translate(" + self.config.cx + ", " + self.config.cy + ") rotate(" + rotation + ")";
241 | }
242 | });
243 | }
244 |
245 | this.valueToDegrees = function(value)
246 | {
247 | // thanks @closealert
248 | //return value / this.config.range * 270 - 45;
249 | return value / this.config.range * 270 - (this.config.min / this.config.range * 270 + 45);
250 | }
251 |
252 | this.valueToRadians = function(value)
253 | {
254 | return this.valueToDegrees(value) * Math.PI / 180;
255 | }
256 |
257 | this.valueToPoint = function(value, factor)
258 | {
259 | return { x: this.config.cx - this.config.raduis * factor * Math.cos(this.valueToRadians(value)),
260 | y: this.config.cy - this.config.raduis * factor * Math.sin(this.valueToRadians(value)) };
261 | }
262 |
263 | // initialization
264 | this.configure(configuration);
265 | }
--------------------------------------------------------------------------------
/app/styles/main.css:
--------------------------------------------------------------------------------
1 | body {
2 | padding-top: 60px;
3 | padding-bottom: 40px;
4 | }
5 | .sidebar-nav {
6 | padding: 9px 0;
7 | }
8 |
9 | @media (max-width: 980px) {
10 | /* Enable use of floated navbar text */
11 | .navbar-text.pull-right {
12 | float: none;
13 | padding-left: 5px;
14 | padding-right: 5px;
15 | }
16 | }
17 |
18 | body {
19 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
20 | color: #333;
21 | }
22 |
23 | .hero-unit {
24 | margin: 50px auto 0 auto;
25 | width: 300px;
26 | font-size: 18px;
27 | font-weight: 200;
28 | line-height: 30px;
29 | background-color: #eee;
30 | border-radius: 6px;
31 | padding: 60px;
32 | }
33 |
34 | .hero-unit h1 {
35 | font-size: 60px;
36 | line-height: 1;
37 | letter-spacing: -1px;
38 | }
39 |
40 | .line-chart {
41 | height: 400px;
42 | }
43 |
44 | .gauge {
45 | margin-top: 80px;
46 | }
47 |
--------------------------------------------------------------------------------
/app/views/main.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Google Line Chart
4 |
5 |
6 |
7 |
D3.js Gauge
8 |
9 |
10 |
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "angularRealTimeCharts",
3 | "version": "0.0.0",
4 | "dependencies": {
5 | "angular": "~1.2.23",
6 | "angular-route": "~1.2.23",
7 | "json3": "~3.2.4",
8 | "jquery": "~1.9.1",
9 | "bootstrap-sass": "~2.3.1",
10 | "es5-shim": "~2.0.8",
11 | "angular-resource": "~1.2.23",
12 | "angular-cookies": "~1.2.23",
13 | "angular-sanitize": "~1.2.23",
14 | "d3": "~3.3.9"
15 | },
16 | "devDependencies": {
17 | "angular-mocks": "~1.2.23",
18 | "angular-scenario": "~1.2.23"
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/doc/realtimecharts.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nickholub/angular-real-time-charts/e800ea9be8e3802c20808468c02db503a50af3c6/doc/realtimecharts.png
--------------------------------------------------------------------------------
/karma-e2e.conf.js:
--------------------------------------------------------------------------------
1 | // Karma configuration
2 | // http://karma-runner.github.io/0.10/config/configuration-file.html
3 |
4 | module.exports = function(config) {
5 | config.set({
6 | // base path, that will be used to resolve files and exclude
7 | basePath: '',
8 |
9 | // testing framework to use (jasmine/mocha/qunit/...)
10 | frameworks: ['ng-scenario'],
11 |
12 | // list of files / patterns to load in the browser
13 | files: [
14 | 'test/e2e/**/*.js'
15 | ],
16 |
17 | // list of files / patterns to exclude
18 | exclude: [],
19 |
20 | // web server port
21 | port: 8080,
22 |
23 | // level of logging
24 | // possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG
25 | logLevel: config.LOG_INFO,
26 |
27 |
28 | // enable / disable watching file and executing tests whenever any file changes
29 | autoWatch: false,
30 |
31 |
32 | // Start these browsers, currently available:
33 | // - Chrome
34 | // - ChromeCanary
35 | // - Firefox
36 | // - Opera
37 | // - Safari (only Mac)
38 | // - PhantomJS
39 | // - IE (only Windows)
40 | browsers: ['Chrome'],
41 |
42 |
43 | // Continuous Integration mode
44 | // if true, it capture browsers, run tests and exit
45 | singleRun: false
46 |
47 | // Uncomment the following lines if you are using grunt's server to run the tests
48 | // proxies: {
49 | // '/': 'http://localhost:9000/'
50 | // },
51 | // URL root prevent conflicts with the site root
52 | // urlRoot: '_karma_'
53 | });
54 | };
55 |
--------------------------------------------------------------------------------
/karma.conf.js:
--------------------------------------------------------------------------------
1 | // Karma configuration
2 | // http://karma-runner.github.io/0.10/config/configuration-file.html
3 |
4 | module.exports = function(config) {
5 | config.set({
6 | // base path, that will be used to resolve files and exclude
7 | basePath: '',
8 |
9 | // testing framework to use (jasmine/mocha/qunit/...)
10 | frameworks: ['jasmine'],
11 |
12 | // list of files / patterns to load in the browser
13 | files: [
14 | 'app/bower_components/jquery/jquery.js',
15 | 'app/bower_components/angular/angular.js',
16 | 'app/bower_components/angular-route/angular-route.js',
17 | 'app/bower_components/angular-mocks/angular-mocks.js',
18 | 'app/bower_components/angular-resource/angular-resource.js',
19 | 'app/bower_components/angular-cookies/angular-cookies.js',
20 | 'app/bower_components/angular-sanitize/angular-sanitize.js',
21 | 'app/scripts/*.js',
22 | 'app/scripts/**/*.js',
23 | 'test/mock/**/*.js',
24 | 'test/spec/**/*.js'
25 | ],
26 |
27 | // list of files / patterns to exclude
28 | exclude: [],
29 |
30 | // web server port
31 | port: 8080,
32 |
33 | // level of logging
34 | // possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG
35 | logLevel: config.LOG_INFO,
36 |
37 |
38 | // enable / disable watching file and executing tests whenever any file changes
39 | autoWatch: false,
40 |
41 |
42 | // Start these browsers, currently available:
43 | // - Chrome
44 | // - ChromeCanary
45 | // - Firefox
46 | // - Opera
47 | // - Safari (only Mac)
48 | // - PhantomJS
49 | // - IE (only Windows)
50 | browsers: ['PhantomJS'],
51 |
52 |
53 | // Continuous Integration mode
54 | // if true, it capture browsers, run tests and exit
55 | singleRun: false
56 | });
57 | };
58 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "angular-real-time-charts",
3 | "version": "0.0.0",
4 | "dependencies": {
5 | "express": "~3.4.3",
6 | "sockjs": "~0.3.8",
7 | "karma-jasmine": "^0.1.5",
8 | "karma-phantomjs-launcher": "^0.1.4"
9 | },
10 | "devDependencies": {
11 | "grunt": "~0.4.1",
12 | "grunt-contrib-copy": "~0.4.1",
13 | "grunt-contrib-concat": "~0.3.0",
14 | "grunt-contrib-coffee": "~0.7.0",
15 | "grunt-contrib-uglify": "~0.2.0",
16 | "grunt-contrib-compass": "~0.5.0",
17 | "grunt-contrib-jshint": "~0.6.0",
18 | "grunt-contrib-cssmin": "~0.6.0",
19 | "grunt-contrib-connect": "~0.5.0",
20 | "grunt-contrib-clean": "~0.5.0",
21 | "grunt-contrib-htmlmin": "~0.1.3",
22 | "grunt-contrib-imagemin": "~0.2.0",
23 | "grunt-contrib-watch": "~0.5.2",
24 | "grunt-autoprefixer": "~0.2.0",
25 | "grunt-usemin": "~0.1.11",
26 | "grunt-svgmin": "~0.2.0",
27 | "grunt-rev": "~0.1.0",
28 | "grunt-concurrent": "~0.3.0",
29 | "load-grunt-tasks": "~0.1.0",
30 | "grunt-google-cdn": "~0.2.0",
31 | "grunt-ngmin": "~0.0.2",
32 | "time-grunt": "~0.1.0",
33 | "karma-ng-scenario": "^0.1.0",
34 | "grunt-karma": "^0.8.3",
35 | "karma": "^0.12.17",
36 | "karma-ng-html2js-preprocessor": "^0.1.0"
37 | },
38 | "engines": {
39 | "node": ">=0.8.0"
40 | },
41 | "scripts": {
42 | "test": "grunt test"
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/test/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "node": true,
3 | "browser": true,
4 | "esnext": true,
5 | "bitwise": true,
6 | "camelcase": true,
7 | "curly": true,
8 | "eqeqeq": true,
9 | "immed": true,
10 | "indent": 2,
11 | "latedef": true,
12 | "newcap": true,
13 | "noarg": true,
14 | "quotmark": "single",
15 | "regexp": true,
16 | "undef": true,
17 | "unused": true,
18 | "strict": true,
19 | "trailing": true,
20 | "smarttabs": true,
21 | "globals": {
22 | "after": false,
23 | "afterEach": false,
24 | "angular": false,
25 | "before": false,
26 | "beforeEach": false,
27 | "browser": false,
28 | "describe": false,
29 | "expect": false,
30 | "inject": false,
31 | "it": false,
32 | "spyOn": false
33 | }
34 | }
35 |
36 |
--------------------------------------------------------------------------------
/test/runner.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | End2end Test Runner
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/test/spec/controllers/main.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | describe('Controller: MainCtrl', function () {
4 |
5 | // load the controller's module
6 | beforeEach(module('app'));
7 |
8 | var MainCtrl,
9 | scope;
10 |
11 | // Initialize the controller and a mock scope
12 | beforeEach(inject(function ($controller, $rootScope) {
13 | scope = $rootScope.$new();
14 | //MainCtrl = $controller('MainCtrl', {
15 | // $scope: scope
16 | //});
17 | }));
18 |
19 | it('should attach a list of awesomeThings to the scope', function () {
20 | //expect(scope.awesomeThings.length).toBe(3);
21 | });
22 | });
23 |
--------------------------------------------------------------------------------
/test/spec/directives/gauge.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | describe('Directive: gauge', function () {
4 |
5 | // load the directive's module
6 | beforeEach(module('app'));
7 |
8 | var element,
9 | scope;
10 |
11 | beforeEach(inject(function ($rootScope) {
12 | scope = $rootScope.$new();
13 | }));
14 |
15 | it('should make hidden element visible', inject(function ($compile) {
16 | //element = angular.element('');
17 | //element = $compile(element)(scope);
18 | //expect(element.text()).toBe('this is the gauge directive');
19 | }));
20 | });
21 |
--------------------------------------------------------------------------------
/test/spec/directives/lineChart.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | describe('Directive: lineChart', function () {
4 |
5 | // load the directive's module
6 | beforeEach(module('app'));
7 |
8 | var element,
9 | scope;
10 |
11 | beforeEach(inject(function ($rootScope) {
12 | scope = $rootScope.$new();
13 | }));
14 |
15 | it('should make hidden element visible', inject(function ($compile) {
16 | //element = angular.element('');
17 | //element = $compile(element)(scope);
18 | //expect(element.text()).toBe('this is the gLineChart directive');
19 | }));
20 | });
21 |
--------------------------------------------------------------------------------
/test/spec/services/websocket.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | describe('Service: webSocket', function () {
4 |
5 | var webSocketObject;
6 | var webSocket;
7 |
8 | beforeEach(module('app.service', function(webSocketProvider) {
9 | webSocketObject = {
10 | send: function () {}
11 | };
12 |
13 | webSocketProvider.setWebSocketObject(webSocketObject);
14 | }));
15 |
16 | beforeEach(inject(function (_webSocket_) {
17 | webSocket = _webSocket_;
18 | }));
19 |
20 | it('should notify subscribers', function () {
21 | expect(webSocketObject.onmessage).toBeDefined();
22 |
23 | var listener1 = jasmine.createSpy();
24 | var listener2 = jasmine.createSpy();
25 |
26 | webSocket.subscribe(listener1);
27 | webSocket.subscribe(listener2);
28 |
29 | webSocketObject.onmessage({ data: '{ "value": 100 }' });
30 |
31 | expect(listener1).toHaveBeenCalledWith({ value: 100 });
32 | expect(listener2).toHaveBeenCalledWith({ value: 100 });
33 |
34 | webSocketObject.onmessage({ data: '{ "value": 50 }' });
35 |
36 | expect(listener1).toHaveBeenCalledWith({ value: 50 });
37 | expect(listener2).toHaveBeenCalledWith({ value: 50 });
38 | });
39 |
40 | it('should send message when WebSocket connection is opened', inject(function ($rootScope) {
41 | expect(webSocketObject.onopen).toBeDefined();
42 |
43 | spyOn(webSocketObject, 'send');
44 |
45 | webSocket.send({ value: 100 });
46 |
47 | expect(webSocketObject.send).not.toHaveBeenCalled(); // no connection yet
48 |
49 | webSocketObject.onopen();
50 | $rootScope.$apply(); // required for AngularJS promise resolution
51 |
52 | expect(webSocketObject.send).toHaveBeenCalled();
53 | }));
54 |
55 | });
56 |
--------------------------------------------------------------------------------