├── .editorconfig ├── .gitattributes ├── .gitignore ├── README.md ├── dashode.jpg ├── main.js ├── package.json └── static ├── css ├── main.css └── rickshaw.css ├── index.html ├── js ├── bandwidthChart.js ├── codeChart.js ├── gauge.js ├── gaugesChart.js ├── loadChart.js ├── main.js └── verbChart.js └── vendors ├── Rickshaw.Graph.Annotate.js ├── d3.v3.js └── rickshaw.js /.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 = 4 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 | 23 | [*.json] 24 | indent_style = space 25 | indent_size = 2 26 | 27 | [.gitconfig] 28 | indent_style = tab 29 | indent_size = 4 30 | 31 | [*.sublime-project] 32 | indent_style = tab 33 | indent_size = 4 34 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Enforce LF line endings 2 | * text eol=lf 3 | 4 | # Text file extensions 5 | *.php text eol=lf 6 | *.js text eol=lf 7 | *.json text eol=lf 8 | *.css text eol=lf 9 | *.less text eol=lf 10 | *.inf text eol=lf 11 | *.txt text 12 | *.html text eol=lf 13 | *.htm text eol=lf 14 | *.xml text eol=lf 15 | 16 | # Scripts 17 | *.cmd text eol=crlf 18 | *.sh text eol=lf 19 | 20 | 21 | # Binary file extensions 22 | *.phar binary 23 | *.dat binary 24 | *.exe binary 25 | *.dll binary 26 | 27 | ## Documents 28 | *.pdf binary 29 | *.doc binary 30 | *.xls binary 31 | *.docx binary 32 | *.xlsx binary 33 | 34 | ## Imgaes 35 | *.webp binary 36 | *.png binary 37 | *.jpg binary 38 | *.jpeg binary 39 | *.gif binary 40 | *.bmp binary 41 | *.ico binary 42 | *.cur binary 43 | *.psd binary 44 | 45 | ## Audio/Video/Flash 46 | *.mp3 binary 47 | *.wav binary 48 | *.flv binary 49 | *.swf binary 50 | *.mp4 binary 51 | 52 | ## Archives 53 | *.rar binary 54 | *.zip binary 55 | *.gz binary 56 | *.7z binary 57 | 58 | ## Fonts 59 | *.ttf binary 60 | *.eot binary 61 | *.woff binary 62 | *.otf binary 63 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | error.log 2 | 3 | isolate-*-v8.log 4 | 5 | npm-debug.log 6 | node_modules/ 7 | 8 | .directory 9 | *.sublime-workspace 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NOTE: https://github.com/apocas/dashode2 - more complex and feature rich (not so user friendly) 2 | 3 | # dashode 4 | 5 | Simple & standalone HTTP requests realtime dashboard for clf based webservers. 6 | 7 | It was designed to debug and monitor nginx instances but it should work with anything that features a clf log. 8 | 9 | ![dashode](https://raw.githubusercontent.com/apocas/dashode/master/dashode.jpg "dashode") 10 | 11 | ## Usage 12 | 13 | ### Install 14 | 15 | * `npm install -g dashode` 16 | * `dashode` (defaults to --log=/var/log/nginx/access.log --port=1337) 17 | * Point your browser to http://hostname:1337 18 | 19 | ### Options 20 | 21 | * dashode --log=/var/log/nginx/access.log --port=1337 22 | 23 | ## Support 24 | 25 | * Should support all web servers that support standard clf log format. 26 | * nginx, apache and others. 27 | 28 | ## Authentication 29 | 30 | * Dashode does not feature authentication, you should push that to a reverse proxy if you need it. 31 | 32 | ### nginx Authentication example 33 | ``` 34 | server { 35 | listen 8080; 36 | server_name ~^(.+)$; 37 | 38 | location / { 39 | auth_basic "Restricted"; 40 | auth_basic_user_file /var/nginx/passwd; 41 | 42 | proxy_set_header Upgrade $http_upgrade; 43 | proxy_set_header Connection "upgrade"; 44 | proxy_http_version 1.1; 45 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 46 | proxy_set_header Host $host; 47 | 48 | proxy_pass http://127.0.0.1:1337/; 49 | } 50 | } 51 | ``` 52 | 53 | ## Design notes 54 | 55 | * Usage must be stupid simple 56 | * Monolithic/self-contained 57 | * Stateless & realtime only 58 | * Behave like a debug tool / bragging dashboard 59 | 60 | ## License 61 | 62 | Pedro Dias - [@pedromdias](https://twitter.com/pedromdias) 63 | 64 | Licensed under the Apache license, version 2.0 (the "license"); You may not use this file except in compliance with the license. You may obtain a copy of the license at: 65 | 66 | http://www.apache.org/licenses/LICENSE-2.0.html 67 | 68 | Unless required by applicable law or agreed to in writing, software distributed under the license is distributed on an "as is" basis, without warranties or conditions of any kind, either express or implied. See the license for the specific language governing permissions and limitations under the license. 69 | -------------------------------------------------------------------------------- /dashode.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apocas/dashode/d1513b000f26db573c4362c4720b09198bd25d0d/dashode.jpg -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var Tail = require('tail').Tail, 4 | parser = require('clf-parser'), 5 | express = require('express'), 6 | os = require('os'); 7 | var argv = require('yargs').argv; 8 | 9 | var port = argv.port || 1337; 10 | var path = argv.path || argv.log || '/var/log/nginx/access.log'; 11 | 12 | var app = express(); 13 | app.use(express.static(__dirname + '/static')); 14 | 15 | var server = require('http').Server(app), 16 | io = require('socket.io')(server), 17 | tail = new Tail(path); 18 | 19 | var requests = []; 20 | 21 | tail.on("line", function(data) { 22 | var req = parser(data); 23 | 24 | // console.log(req); 25 | 26 | if (req.status !== undefined && req.body_bytes_sent !== undefined && req.http_method !== undefined) { 27 | var aux = {}; 28 | 29 | aux.status = req.status; 30 | aux.body_bytes_sent = req.body_bytes_sent; 31 | aux.http_method = req.http_method; 32 | 33 | requests.push(aux); 34 | } 35 | }); 36 | 37 | tail.on("error", function(error) { 38 | console.log('TAIL ERROR: ', error); 39 | }); 40 | 41 | checkTailWatch(); 42 | 43 | setInterval(function() { 44 | io.sockets.emit('data', { 45 | 'info': { 46 | 'load': os.loadavg(), 47 | 'totalmem': os.totalmem(), 48 | 'freemem': os.freemem(), 49 | 'loadpercentage': parseInt((os.loadavg()[0] * 100) / os.cpus().length) 50 | }, 51 | 'requests': requests 52 | }); 53 | requests = []; 54 | }, 1000); 55 | 56 | server.listen(port); 57 | 58 | io.on('connection', function(socket) { 59 | console.log('Client connected'); 60 | 61 | checkTailWatch(); 62 | 63 | socket.on('disconnect', function (reason) { 64 | console.log('Client disconnected:', reason); 65 | checkTailWatch(); 66 | }); 67 | }); 68 | 69 | function checkTailWatch() { 70 | if ( io.sockets.sockets.length ) { 71 | tail.watch(); 72 | } 73 | else { 74 | tail.unwatch(); 75 | } 76 | } 77 | 78 | console.log('########################'); 79 | console.log('dashode'); 80 | console.log('########################'); 81 | console.log('Server started!'); 82 | console.log('Listening on port: ' + port); 83 | console.log('Watching log: ' + path); 84 | console.log('----'); 85 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dashode", 3 | "description": "Simple & standalone HTTP requests realtime dashboard for clf based webservers.", 4 | "version": "1.0.3", 5 | "author": "Pedro Dias ", 6 | "maintainers": [ 7 | "apocas " 8 | ], 9 | "dependencies": { 10 | "tail": "1.2.x", 11 | "clf-parser": "0.0.x", 12 | "express": "4.16.x", 13 | "socket.io": "1.3.x", 14 | "yargs": "3.0.x" 15 | }, 16 | "main": "./main", 17 | "engines": { 18 | "node": ">= 0.8" 19 | }, 20 | "bin": { 21 | "dashode": "./main.js" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /static/css/main.css: -------------------------------------------------------------------------------- 1 | * { padding: 0; margin: 0; vertical-align: top; } 2 | 3 | body { 4 | font: 18px/1.5em "proxima-nova", Helvetica, Arial, sans-serif; 5 | } 6 | 7 | a { color: #069; } 8 | a:hover { color: #28b; } 9 | 10 | h2 { 11 | margin-top: 15px; 12 | font: normal 32px "omnes-pro", Helvetica, Arial, sans-serif; 13 | } 14 | 15 | h3 { 16 | margin-left: 30px; 17 | font: normal 26px "omnes-pro", Helvetica, Arial, sans-serif; 18 | color: #666; 19 | } 20 | 21 | p { 22 | margin-top: 10px; 23 | } 24 | 25 | button { 26 | font-size: 18px; 27 | padding: 1px 7px; 28 | } 29 | 30 | input { 31 | font-size: 18px; 32 | } 33 | 34 | input[type=checkbox] { 35 | margin: 7px; 36 | } 37 | 38 | #header { 39 | position: relative; 40 | width: 900px; 41 | margin: auto; 42 | } 43 | 44 | #header h2 { 45 | margin-left: 10px; 46 | vertical-align: middle; 47 | font-size: 42px; 48 | font-weight: bold; 49 | text-decoration: none; 50 | color: #000; 51 | } 52 | 53 | .content { 54 | float: left; 55 | } 56 | 57 | #footer { 58 | float: left; 59 | width: 100%; 60 | text-align: center; 61 | font-size: 12px; 62 | color: #999; 63 | } 64 | 65 | .container { 66 | box-sizing: border-box; 67 | width: 100%; 68 | padding: 0px 5px 0px 5px; 69 | background: #fff; 70 | background: linear-gradient(#f6f6f6 0, #fff 50px); 71 | background: -o-linear-gradient(#f6f6f6 0, #fff 50px); 72 | background: -ms-linear-gradient(#f6f6f6 0, #fff 50px); 73 | background: -moz-linear-gradient(#f6f6f6 0, #fff 50px); 74 | background: -webkit-linear-gradient(#f6f6f6 0, #fff 50px); 75 | box-shadow: 0 3px 10px rgba(0,0,0,0.15); 76 | -o-box-shadow: 0 3px 10px rgba(0,0,0,0.1); 77 | -ms-box-shadow: 0 3px 10px rgba(0,0,0,0.1); 78 | -moz-box-shadow: 0 3px 10px rgba(0,0,0,0.1); 79 | -webkit-box-shadow: 0 3px 10px rgba(0,0,0,0.1); 80 | position: relative; 81 | } 82 | 83 | .placeholder { 84 | width: 100%; 85 | height: 100%; 86 | font-size: 14px; 87 | line-height: 1.2em; 88 | } 89 | 90 | .legend table { 91 | border-spacing: 5px; 92 | } 93 | -------------------------------------------------------------------------------- /static/css/rickshaw.css: -------------------------------------------------------------------------------- 1 | .rickshaw_graph .detail { 2 | pointer-events: none; 3 | position: absolute; 4 | top: 0; 5 | z-index: 2; 6 | background: rgba(0, 0, 0, 0.1); 7 | bottom: 0; 8 | width: 1px; 9 | transition: opacity 0.25s linear; 10 | -moz-transition: opacity 0.25s linear; 11 | -o-transition: opacity 0.25s linear; 12 | -webkit-transition: opacity 0.25s linear; 13 | } 14 | .rickshaw_graph .detail.inactive { 15 | opacity: 0; 16 | } 17 | .rickshaw_graph .detail .item.active { 18 | opacity: 1; 19 | } 20 | .rickshaw_graph .detail .x_label { 21 | font-family: Arial, sans-serif; 22 | border-radius: 3px; 23 | padding: 6px; 24 | opacity: 0.5; 25 | border: 1px solid #e0e0e0; 26 | font-size: 12px; 27 | position: absolute; 28 | background: white; 29 | white-space: nowrap; 30 | } 31 | .rickshaw_graph .detail .x_label.left { 32 | left: 0; 33 | } 34 | .rickshaw_graph .detail .x_label.right { 35 | right: 0; 36 | } 37 | .rickshaw_graph .detail .item { 38 | position: absolute; 39 | z-index: 2; 40 | border-radius: 3px; 41 | padding: 0.25em; 42 | font-size: 12px; 43 | font-family: Arial, sans-serif; 44 | opacity: 0; 45 | background: rgba(0, 0, 0, 0.4); 46 | color: white; 47 | border: 1px solid rgba(0, 0, 0, 0.4); 48 | margin-left: 1em; 49 | margin-right: 1em; 50 | margin-top: -1em; 51 | white-space: nowrap; 52 | } 53 | .rickshaw_graph .detail .item.left { 54 | left: 0; 55 | } 56 | .rickshaw_graph .detail .item.right { 57 | right: 0; 58 | } 59 | .rickshaw_graph .detail .item.active { 60 | opacity: 1; 61 | background: rgba(0, 0, 0, 0.8); 62 | } 63 | .rickshaw_graph .detail .item:after { 64 | position: absolute; 65 | display: block; 66 | width: 0; 67 | height: 0; 68 | 69 | content: ""; 70 | 71 | border: 5px solid transparent; 72 | } 73 | .rickshaw_graph .detail .item.left:after { 74 | top: 1em; 75 | left: -5px; 76 | margin-top: -5px; 77 | border-right-color: rgba(0, 0, 0, 0.8); 78 | border-left-width: 0; 79 | } 80 | .rickshaw_graph .detail .item.right:after { 81 | top: 1em; 82 | right: -5px; 83 | margin-top: -5px; 84 | border-left-color: rgba(0, 0, 0, 0.8); 85 | border-right-width: 0; 86 | } 87 | .rickshaw_graph .detail .dot { 88 | width: 4px; 89 | height: 4px; 90 | margin-left: -3px; 91 | margin-top: -3.5px; 92 | border-radius: 5px; 93 | position: absolute; 94 | box-shadow: 0 0 2px rgba(0, 0, 0, 0.6); 95 | box-sizing: content-box; 96 | -moz-box-sizing: content-box; 97 | background: white; 98 | border-width: 2px; 99 | border-style: solid; 100 | display: none; 101 | background-clip: padding-box; 102 | } 103 | .rickshaw_graph .detail .dot.active { 104 | display: block; 105 | } 106 | 107 | 108 | 109 | 110 | 111 | .rickshaw_legend { 112 | font-family: Arial; 113 | font-size: 12px; 114 | color: #404040; 115 | display: inline-block; 116 | padding: 12px 5px; 117 | border-radius: 2px; 118 | position: absolute; 119 | top: 0px; 120 | } 121 | 122 | .rickshaw_legend:hover { 123 | z-index: 10; 124 | } 125 | 126 | .rickshaw_legend .swatch { 127 | width: 10px; 128 | height: 10px; 129 | border: 1px solid rgba(0, 0, 0, 0.2); 130 | } 131 | 132 | .rickshaw_legend .line { 133 | clear: both; 134 | line-height: 140%; 135 | padding-right: 15px; 136 | display: inline; 137 | } 138 | 139 | .rickshaw_legend .line .swatch { 140 | display: inline-block; 141 | margin-right: 3px; 142 | border-radius: 2px; 143 | } 144 | 145 | .rickshaw_legend .label { 146 | margin: 0; 147 | white-space: nowrap; 148 | display: inline; 149 | font-size: inherit; 150 | background-color: transparent; 151 | color: inherit; 152 | font-weight: normal; 153 | line-height: normal; 154 | padding: 0px; 155 | text-shadow: none; 156 | } 157 | 158 | .rickshaw_legend .action:hover { 159 | opacity: 0.6; 160 | } 161 | 162 | .rickshaw_legend .action { 163 | margin-right: 0.2em; 164 | font-size: 10px; 165 | opacity: 0.2; 166 | cursor: pointer; 167 | font-size: 14px; 168 | } 169 | 170 | .rickshaw_legend .line.disabled { 171 | opacity: 0.4; 172 | } 173 | 174 | .rickshaw_legend ul { 175 | list-style-type: none; 176 | margin: 0; 177 | padding: 0; 178 | margin: 2px; 179 | cursor: pointer; 180 | } 181 | 182 | .rickshaw_legend li { 183 | padding: 0 0 0 2px; 184 | min-width: 80px; 185 | white-space: nowrap; 186 | } 187 | 188 | .rickshaw_legend li:hover { 189 | background: rgba(255, 255, 255, 0.08); 190 | border-radius: 3px; 191 | } 192 | 193 | .rickshaw_legend li:active { 194 | background: rgba(255, 255, 255, 0.2); 195 | border-radius: 3px; 196 | } 197 | 198 | 199 | /* graph */ 200 | 201 | .rickshaw_graph { 202 | position: relative; 203 | } 204 | 205 | .rickshaw_graph svg { 206 | display: block; 207 | overflow: hidden; 208 | } 209 | 210 | 211 | /* ticks */ 212 | 213 | .rickshaw_graph .x_tick { 214 | position: absolute; 215 | top: 0; 216 | bottom: 0; 217 | width: 0px; 218 | border-left: 1px dotted rgba(0, 0, 0, 0.2); 219 | pointer-events: none; 220 | } 221 | 222 | .rickshaw_graph .x_tick .title { 223 | position: absolute; 224 | font-size: 12px; 225 | font-family: Arial, sans-serif; 226 | opacity: 0.5; 227 | white-space: nowrap; 228 | margin-left: 3px; 229 | bottom: 1px; 230 | } 231 | 232 | 233 | /* annotations */ 234 | 235 | .rickshaw_annotation_timeline { 236 | height: 1px; 237 | border-top: 1px solid #e0e0e0; 238 | margin-top: 10px; 239 | position: relative; 240 | } 241 | 242 | .rickshaw_annotation_timeline .annotation { 243 | position: absolute; 244 | height: 6px; 245 | width: 6px; 246 | margin-left: -2px; 247 | top: -3px; 248 | border-radius: 5px; 249 | background-color: rgba(0, 0, 0, 0.25); 250 | } 251 | 252 | .rickshaw_graph .annotation_line { 253 | position: absolute; 254 | top: 0; 255 | bottom: -6px; 256 | width: 0px; 257 | border-left: 2px solid rgba(0, 0, 0, 0.3); 258 | display: none; 259 | } 260 | 261 | .rickshaw_graph .annotation_line.active { 262 | display: block; 263 | } 264 | 265 | .rickshaw_graph .annotation_range { 266 | background: rgba(0, 0, 0, 0.1); 267 | display: none; 268 | position: absolute; 269 | top: 0; 270 | bottom: -6px; 271 | } 272 | 273 | .rickshaw_graph .annotation_range.active { 274 | display: block; 275 | } 276 | 277 | .rickshaw_graph .annotation_range.active.offscreen { 278 | display: none; 279 | } 280 | 281 | .rickshaw_annotation_timeline .annotation .content { 282 | background: white; 283 | color: black; 284 | opacity: 0.9; 285 | padding: 5px 5px; 286 | box-shadow: 0 0 2px rgba(0, 0, 0, 0.8); 287 | border-radius: 3px; 288 | position: relative; 289 | z-index: 20; 290 | font-size: 12px; 291 | padding: 6px 8px 8px; 292 | top: -48px; 293 | left: -11px; 294 | display: none; 295 | cursor: pointer; 296 | } 297 | 298 | .rickshaw_annotation_timeline .annotation .content:before { 299 | content: "\25b2"; 300 | position: absolute; 301 | top: 30px; 302 | color: white; 303 | text-shadow: 0 -1px 1px rgba(0, 0, 0, 0.8); 304 | } 305 | 306 | .rickshaw_annotation_timeline .annotation.active, 307 | .rickshaw_annotation_timeline .annotation:hover { 308 | background-color: rgba(0, 0, 0, 0.8); 309 | cursor: none; 310 | } 311 | 312 | .rickshaw_annotation_timeline .annotation .content:hover { 313 | z-index: 50; 314 | } 315 | 316 | .rickshaw_annotation_timeline .annotation.active .content { 317 | display: block; 318 | } 319 | 320 | .rickshaw_annotation_timeline .annotation:hover .content { 321 | display: block; 322 | z-index: 50; 323 | } 324 | 325 | .rickshaw_graph .y_axis, 326 | .rickshaw_graph .x_axis_d3 { 327 | fill: none; 328 | } 329 | 330 | .rickshaw_graph .y_ticks .tick line, 331 | .rickshaw_graph .x_ticks_d3 .tick { 332 | stroke: rgba(0, 0, 0, 0.16); 333 | stroke-width: 2px; 334 | shape-rendering: crisp-edges; 335 | pointer-events: none; 336 | } 337 | 338 | .rickshaw_graph .y_grid .tick, 339 | .rickshaw_graph .x_grid_d3 .tick { 340 | z-index: -1; 341 | stroke: rgba(0, 0, 0, 0.20); 342 | stroke-width: 1px; 343 | stroke-dasharray: 1 1; 344 | } 345 | 346 | .rickshaw_graph .y_grid .tick[data-y-value="0"] { 347 | stroke-dasharray: 1 0; 348 | } 349 | 350 | .rickshaw_graph .y_grid path, 351 | .rickshaw_graph .x_grid_d3 path { 352 | fill: none; 353 | stroke: none; 354 | } 355 | 356 | .rickshaw_graph .y_ticks path, 357 | .rickshaw_graph .x_ticks_d3 path { 358 | fill: none; 359 | stroke: #808080; 360 | } 361 | 362 | .rickshaw_graph .y_ticks text, 363 | .rickshaw_graph .x_ticks_d3 text { 364 | opacity: 0.5; 365 | font-size: 12px; 366 | pointer-events: none; 367 | } 368 | 369 | .rickshaw_graph .x_tick.glow .title, 370 | .rickshaw_graph .y_ticks.glow text { 371 | fill: black; 372 | color: black; 373 | text-shadow: -1px 1px 0 rgba(255, 255, 255, 0.1), 1px -1px 0 rgba(255, 255, 255, 0.1), 1px 1px 0 rgba(255, 255, 255, 0.1), 0px 1px 0 rgba(255, 255, 255, 0.1), 0px -1px 0 rgba(255, 255, 255, 0.1), 1px 0px 0 rgba(255, 255, 255, 0.1), -1px 0px 0 rgba(255, 255, 255, 0.1), -1px -1px 0 rgba(255, 255, 255, 0.1); 374 | } 375 | 376 | .rickshaw_graph .x_tick.inverse .title, 377 | .rickshaw_graph .y_ticks.inverse text { 378 | fill: white; 379 | color: white; 380 | text-shadow: -1px 1px 0 rgba(0, 0, 0, 0.8), 1px -1px 0 rgba(0, 0, 0, 0.8), 1px 1px 0 rgba(0, 0, 0, 0.8), 0px 1px 0 rgba(0, 0, 0, 0.8), 0px -1px 0 rgba(0, 0, 0, 0.8), 1px 0px 0 rgba(0, 0, 0, 0.8), -1px 0px 0 rgba(0, 0, 0, 0.8), -1px -1px 0 rgba(0, 0, 0, 0.8); 381 | } 382 | -------------------------------------------------------------------------------- /static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | dashode 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 | 46 |
47 |
48 | 49 |
50 |
51 | 52 |
53 |
54 | 55 |
56 |
57 | 58 |
59 |
60 | 61 |
62 |
63 | 64 | 67 | 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /static/js/bandwidthChart.js: -------------------------------------------------------------------------------- 1 | var BandwidthChart = function(placeholder, opts) { 2 | this.graphSize = 150; 3 | this.interval = 1000; 4 | this.placeholder = placeholder; 5 | 6 | if (opts) { 7 | this.graphSize = opts.size || this.graphSize; 8 | this.interval = opts.interval || this.interval; 9 | } 10 | 11 | this.points = [{ 12 | 'name': 'MB', 13 | 'color': '#CDD452', 14 | 'data': [] 15 | }]; 16 | }; 17 | 18 | BandwidthChart.prototype.init = function() { 19 | var self = this; 20 | 21 | this.graph = new Rickshaw.Graph({ 22 | element: document.getElementById(self.placeholder), 23 | renderer: 'line', 24 | series: self.points 25 | }); 26 | 27 | this.xAxis = new Rickshaw.Graph.Axis.Time({ 28 | graph: self.graph, 29 | ticksTreatment: 'glow' 30 | }); 31 | 32 | this.yAxis = new Rickshaw.Graph.Axis.Y({ 33 | graph: self.graph, 34 | tickFormat: function(y) { 35 | var abs_y = Math.abs(y); 36 | if (abs_y === 0) { 37 | return ''; 38 | } else { 39 | return y; 40 | } 41 | }, 42 | ticks: 5, 43 | ticksTreatment: 'glow' 44 | }); 45 | 46 | var legend = new Rickshaw.Graph.Legend({ 47 | graph: self.graph, 48 | element: document.getElementById(self.placeholder + 'Legend') 49 | }); 50 | 51 | var hoverDetail = new Rickshaw.Graph.HoverDetail({ 52 | graph: self.graph, 53 | xFormatter: function(x) { 54 | return new Date(x * 1000).toString(); 55 | } 56 | }); 57 | }; 58 | 59 | BandwidthChart.prototype.draw = function() { 60 | var self = this; 61 | this.graph.configure({ 62 | width: $('#' + self.placeholder).width(), 63 | height: $('#' + self.placeholder).height() 64 | }); 65 | this.graph.render(); 66 | this.xAxis.render(); 67 | this.yAxis.render(); 68 | }; 69 | 70 | BandwidthChart.prototype.appendData = function(data) { 71 | this.formatData(data); 72 | this.draw(); 73 | }; 74 | 75 | BandwidthChart.prototype.formatData = function(data) { 76 | var counter = 0; 77 | var d = new Date(); 78 | 79 | var total = 0; 80 | 81 | for (var i = 0; i < data.length; i++) { 82 | var req = data[i]; 83 | 84 | if (req.body_bytes_sent) { 85 | total += req.body_bytes_sent; 86 | } 87 | } 88 | 89 | this.points[0].data.push({ 90 | 'x': parseInt(d.getTime() / 1000), 91 | 'y': total / 125000 92 | }); 93 | 94 | if (this.points[0].data.length > this.graphSize) { 95 | this.points[0].data.shift(); 96 | } 97 | }; 98 | -------------------------------------------------------------------------------- /static/js/codeChart.js: -------------------------------------------------------------------------------- 1 | var CodeChart = function(placeholder, opts) { 2 | this.graphSize = 150; 3 | this.interval = 1000; 4 | this.placeholder = placeholder; 5 | 6 | if (opts) { 7 | this.graphSize = opts.size || this.graphSize; 8 | this.interval = opts.interval || this.interval; 9 | } 10 | 11 | this.points = [{ 12 | 'name': '2xx', 13 | 'color': '#CDD452', 14 | 'data': [] 15 | }, { 16 | 'name': '3xx', 17 | 'color': '#FEE169', 18 | 'data': [] 19 | }, { 20 | 'name': '4xx', 21 | 'color': '#F9722E', 22 | 'data': [] 23 | }, { 24 | 'name': '5xx', 25 | 'color': '#C9313D', 26 | 'data': [] 27 | }, { 28 | 'name': 'other', 29 | 'color': '#68776C', 30 | 'data': [] 31 | }]; 32 | }; 33 | 34 | CodeChart.prototype.init = function() { 35 | var self = this; 36 | 37 | this.graph = new Rickshaw.Graph({ 38 | element: document.getElementById(self.placeholder), 39 | renderer: 'area', 40 | stroke: true, 41 | series: self.points 42 | }); 43 | 44 | this.xAxis = new Rickshaw.Graph.Axis.Time({ 45 | graph: self.graph, 46 | ticksTreatment: 'glow' 47 | }); 48 | 49 | this.yAxis = new Rickshaw.Graph.Axis.Y({ 50 | graph: self.graph, 51 | tickFormat: function(y) { 52 | var abs_y = Math.abs(y); 53 | if (abs_y === 0) { 54 | return ''; 55 | } else { 56 | return y; 57 | } 58 | }, 59 | ticks: 5, 60 | ticksTreatment: 'glow' 61 | }); 62 | 63 | var legend = new Rickshaw.Graph.Legend({ 64 | graph: self.graph, 65 | element: document.getElementById(self.placeholder + 'Legend') 66 | }); 67 | 68 | var hoverDetail = new Rickshaw.Graph.HoverDetail({ 69 | graph: self.graph, 70 | xFormatter: function(x) { 71 | return new Date(x * 1000).toString(); 72 | } 73 | }); 74 | 75 | var shelving = new Rickshaw.Graph.Behavior.Series.Toggle({ 76 | graph: self.graph, 77 | legend: legend 78 | }); 79 | 80 | var order = new Rickshaw.Graph.Behavior.Series.Order({ 81 | graph: self.graph, 82 | legend: legend 83 | }); 84 | 85 | var highlighter = new Rickshaw.Graph.Behavior.Series.Highlight({ 86 | graph: self.graph, 87 | legend: legend 88 | }); 89 | }; 90 | 91 | CodeChart.prototype.draw = function() { 92 | var self = this; 93 | this.graph.configure({ 94 | width: $('#' + self.placeholder).width(), 95 | height: $('#' + self.placeholder).height(), 96 | unstack: false 97 | }); 98 | this.graph.render(); 99 | this.xAxis.render(); 100 | this.yAxis.render(); 101 | }; 102 | 103 | CodeChart.prototype.appendData = function(data) { 104 | this.formatData(data); 105 | this.draw(); 106 | }; 107 | 108 | CodeChart.prototype.formatData = function(data) { 109 | var counter = 0; 110 | var d = new Date(); 111 | 112 | var codes = { 113 | '200': 0, 114 | '300': 0, 115 | '400': 0, 116 | '500': 0, 117 | 'other': 0 118 | }; 119 | 120 | for (var i = 0; i < data.length; i++) { 121 | var req = data[i]; 122 | var code = 'other'; 123 | 124 | if (req.status >= 200 && req.status < 300) { 125 | codes['200']++; 126 | } else if (req.status >= 300 && req.status < 400) { 127 | codes['300']++; 128 | } else if (req.status >= 400 && req.status < 500) { 129 | codes['400']++; 130 | } else if (req.status >= 500 && req.status < 600) { 131 | codes['500']++; 132 | } else { 133 | codes.other++; 134 | } 135 | } 136 | 137 | for (var property in codes) { 138 | if (codes.hasOwnProperty(property)) { 139 | this.points[counter].data.push({ 140 | 'x': parseInt(d.getTime() / 1000), 141 | 'y': codes[property] 142 | }); 143 | 144 | if (this.points[counter].data.length > this.graphSize) { 145 | this.points[counter].data.shift(); 146 | } 147 | counter++; 148 | } 149 | } 150 | }; 151 | -------------------------------------------------------------------------------- /static/js/gauge.js: -------------------------------------------------------------------------------- 1 | function Gauge(placeholderName, configuration) { 2 | this.placeholderName = placeholderName; 3 | 4 | var self = this; // for internal d3 functions 5 | 6 | this.configure = function(configuration) { 7 | this.config = configuration; 8 | 9 | this.config.size = this.config.size * 0.9; 10 | 11 | this.config.raduis = this.config.size * 0.97 / 2; 12 | this.config.cx = this.config.size / 2; 13 | this.config.cy = this.config.size / 2; 14 | 15 | this.config.min = undefined != configuration.min ? configuration.min : 0; 16 | this.config.max = undefined != configuration.max ? configuration.max : 100; 17 | this.config.range = this.config.max - this.config.min; 18 | 19 | this.config.majorTicks = configuration.majorTicks || 5; 20 | this.config.minorTicks = configuration.minorTicks || 2; 21 | 22 | this.config.greenColor = configuration.greenColor || "#109618"; 23 | this.config.yellowColor = configuration.yellowColor || "#FF9900"; 24 | this.config.redColor = configuration.redColor || "#DC3912"; 25 | 26 | this.config.transitionDuration = configuration.transitionDuration || 500; 27 | } 28 | 29 | this.render = function() { 30 | $("#" + this.placeholderName).html(''); 31 | 32 | this.body = d3.select("#" + this.placeholderName) 33 | .append("svg:svg") 34 | .attr("class", "gauge") 35 | .attr("width", this.config.size) 36 | .attr("height", this.config.size); 37 | 38 | this.body.append("svg:circle") 39 | .attr("cx", this.config.cx) 40 | .attr("cy", this.config.cy) 41 | .attr("r", this.config.raduis) 42 | .style("fill", "#ccc") 43 | .style("stroke", "#000") 44 | .style("stroke-width", "0.5px"); 45 | 46 | this.body.append("svg:circle") 47 | .attr("cx", this.config.cx) 48 | .attr("cy", this.config.cy) 49 | .attr("r", 0.9 * this.config.raduis) 50 | .style("fill", "#fff") 51 | .style("stroke", "#e0e0e0") 52 | .style("stroke-width", "2px"); 53 | 54 | for (var index in this.config.greenZones) { 55 | this.drawBand(this.config.greenZones[index].from, this.config.greenZones[index].to, self.config.greenColor); 56 | } 57 | 58 | for (var index in this.config.yellowZones) { 59 | this.drawBand(this.config.yellowZones[index].from, this.config.yellowZones[index].to, self.config.yellowColor); 60 | } 61 | 62 | for (var index in this.config.redZones) { 63 | this.drawBand(this.config.redZones[index].from, this.config.redZones[index].to, self.config.redColor); 64 | } 65 | 66 | if (undefined != this.config.label) { 67 | var fontSize = Math.round(this.config.size / 9); 68 | this.body.append("svg:text") 69 | .attr("x", this.config.cx) 70 | .attr("y", this.config.cy / 2 + fontSize / 2) 71 | .attr("dy", fontSize / 2) 72 | .attr("text-anchor", "middle") 73 | .text(this.config.label) 74 | .style("font-size", fontSize + "px") 75 | .style("fill", "#333") 76 | .style("stroke-width", "0px"); 77 | } 78 | 79 | var fontSize = Math.round(this.config.size / 16); 80 | var majorDelta = this.config.range / (this.config.majorTicks - 1); 81 | for (var major = this.config.min; major <= this.config.max; major += majorDelta) { 82 | var minorDelta = majorDelta / this.config.minorTicks; 83 | for (var minor = major + minorDelta; minor < Math.min(major + majorDelta, this.config.max); minor += minorDelta) { 84 | var point1 = this.valueToPoint(minor, 0.75); 85 | var point2 = this.valueToPoint(minor, 0.85); 86 | 87 | this.body.append("svg:line") 88 | .attr("x1", point1.x) 89 | .attr("y1", point1.y) 90 | .attr("x2", point2.x) 91 | .attr("y2", point2.y) 92 | .style("stroke", "#666") 93 | .style("stroke-width", "1px"); 94 | } 95 | 96 | var point1 = this.valueToPoint(major, 0.7); 97 | var point2 = this.valueToPoint(major, 0.85); 98 | 99 | this.body.append("svg:line") 100 | .attr("x1", point1.x) 101 | .attr("y1", point1.y) 102 | .attr("x2", point2.x) 103 | .attr("y2", point2.y) 104 | .style("stroke", "#333") 105 | .style("stroke-width", "2px"); 106 | 107 | if (major == this.config.min || major == this.config.max) { 108 | var point = this.valueToPoint(major, 0.63); 109 | 110 | this.body.append("svg:text") 111 | .attr("x", point.x) 112 | .attr("y", point.y) 113 | .attr("dy", fontSize / 3) 114 | .attr("text-anchor", major == this.config.min ? "start" : "end") 115 | .text(major) 116 | .style("font-size", fontSize + "px") 117 | .style("fill", "#333") 118 | .style("stroke-width", "0px"); 119 | } 120 | } 121 | 122 | var pointerContainer = this.body.append("svg:g").attr("class", "pointerContainer"); 123 | 124 | var midValue = (this.config.min + this.config.max) / 2; 125 | 126 | var pointerPath = this.buildPointerPath(midValue); 127 | 128 | var pointerLine = d3.svg.line() 129 | .x(function(d) { 130 | return d.x 131 | }) 132 | .y(function(d) { 133 | return d.y 134 | }) 135 | .interpolate("basis"); 136 | 137 | pointerContainer.selectAll("path") 138 | .data([pointerPath]) 139 | .enter() 140 | .append("svg:path") 141 | .attr("d", pointerLine) 142 | .style("fill", "#dc3912") 143 | .style("stroke", "#c63310") 144 | .style("fill-opacity", 0.7) 145 | 146 | pointerContainer.append("svg:circle") 147 | .attr("cx", this.config.cx) 148 | .attr("cy", this.config.cy) 149 | .attr("r", 0.12 * this.config.raduis) 150 | .style("fill", "#4684EE") 151 | .style("stroke", "#666") 152 | .style("opacity", 1); 153 | 154 | var fontSize = Math.round(this.config.size / 10); 155 | pointerContainer.selectAll("text") 156 | .data([midValue]) 157 | .enter() 158 | .append("svg:text") 159 | .attr("x", this.config.cx) 160 | .attr("y", this.config.size - this.config.cy / 4 - fontSize) 161 | .attr("dy", fontSize / 2) 162 | .attr("text-anchor", "middle") 163 | .style("font-size", fontSize + "px") 164 | .style("fill", "#000") 165 | .style("stroke-width", "0px"); 166 | 167 | this.redraw(this.config.min, 0); 168 | } 169 | 170 | this.buildPointerPath = function(value) { 171 | var delta = this.config.range / 13; 172 | 173 | var head = valueToPoint(value, 0.85); 174 | var head1 = valueToPoint(value - delta, 0.12); 175 | var head2 = valueToPoint(value + delta, 0.12); 176 | 177 | var tailValue = value - (this.config.range * (1 / (270 / 360)) / 2); 178 | var tail = valueToPoint(tailValue, 0.28); 179 | var tail1 = valueToPoint(tailValue - delta, 0.12); 180 | var tail2 = valueToPoint(tailValue + delta, 0.12); 181 | 182 | return [head, head1, tail2, tail, tail1, head2, head]; 183 | 184 | function valueToPoint(value, factor) { 185 | var point = self.valueToPoint(value, factor); 186 | point.x -= self.config.cx; 187 | point.y -= self.config.cy; 188 | return point; 189 | } 190 | } 191 | 192 | this.drawBand = function(start, end, color) { 193 | if (0 >= end - start) return; 194 | 195 | this.body.append("svg:path") 196 | .style("fill", color) 197 | .attr("d", d3.svg.arc() 198 | .startAngle(this.valueToRadians(start)) 199 | .endAngle(this.valueToRadians(end)) 200 | .innerRadius(0.65 * this.config.raduis) 201 | .outerRadius(0.85 * this.config.raduis)) 202 | .attr("transform", function() { 203 | return "translate(" + self.config.cx + ", " + self.config.cy + ") rotate(270)" 204 | }); 205 | } 206 | 207 | this.redraw = function(valuei, transitionDuration) { 208 | var value = valuei || this.data; 209 | var pointerContainer = this.body.select(".pointerContainer"); 210 | 211 | if (this.config.decimal) { 212 | pointerContainer.selectAll("text").text(Math.round(value * 10) / 10); 213 | } else { 214 | pointerContainer.selectAll("text").text(Math.round(value)); 215 | } 216 | 217 | var pointer = pointerContainer.selectAll("path"); 218 | pointer.transition() 219 | .duration(undefined != transitionDuration ? transitionDuration : this.config.transitionDuration) 220 | //.delay(0) 221 | //.ease("linear") 222 | //.attr("transform", function(d) 223 | .attrTween("transform", function() { 224 | var pointerValue = value; 225 | if (value > self.config.max) pointerValue = self.config.max + 0.02 * self.config.range; 226 | else if (value < self.config.min) pointerValue = self.config.min - 0.02 * self.config.range; 227 | var targetRotation = (self.valueToDegrees(pointerValue) - 90); 228 | var currentRotation = self._currentRotation || targetRotation; 229 | self._currentRotation = targetRotation; 230 | 231 | return function(step) { 232 | var rotation = currentRotation + (targetRotation - currentRotation) * step; 233 | return "translate(" + self.config.cx + ", " + self.config.cy + ") rotate(" + rotation + ")"; 234 | } 235 | }); 236 | } 237 | 238 | this.valueToDegrees = function(value) { 239 | // thanks @closealert 240 | //return value / this.config.range * 270 - 45; 241 | return value / this.config.range * 270 - (this.config.min / this.config.range * 270 + 45); 242 | } 243 | 244 | this.valueToRadians = function(value) { 245 | return this.valueToDegrees(value) * Math.PI / 180; 246 | } 247 | 248 | this.valueToPoint = function(value, factor) { 249 | return { 250 | x: this.config.cx - this.config.raduis * factor * Math.cos(this.valueToRadians(value)), 251 | y: this.config.cy - this.config.raduis * factor * Math.sin(this.valueToRadians(value)) 252 | }; 253 | } 254 | 255 | // initialization 256 | this.configure(configuration); 257 | } 258 | -------------------------------------------------------------------------------- /static/js/gaugesChart.js: -------------------------------------------------------------------------------- 1 | var GaugesChart = function(opts) { 2 | this.graphSize = 150; 3 | this.interval = 1000; 4 | 5 | if (opts) { 6 | this.graphSize = opts.size || this.graphSize; 7 | this.interval = opts.interval || this.interval; 8 | } 9 | 10 | this.gauges = {}; 11 | }; 12 | 13 | GaugesChart.prototype.init = function() { 14 | var self = this; 15 | 16 | this.gauges.requests = this.createGauge('requestsGauge', 'Req/s', 0, 50); 17 | this.gauges.bw = this.createGauge('bwGauge', 'MBps', 0, 1, true); 18 | this.gauges.errors = this.createGauge('errorsGauge', 'Error %', 0, 100); 19 | this.gauges.load = this.createGauge('loadGauge', 'Load', 0, 1, true); 20 | this.gauges.mem = this.createGauge('memGauge', 'Mem %', 0, 100); 21 | }; 22 | 23 | GaugesChart.prototype.createGauge = function(container, label, min, max, decimalc) { 24 | var self = this; 25 | 26 | var config = { 27 | size: self.graphSize, 28 | label: label, 29 | min: undefined != min ? min : 0, 30 | max: undefined != max ? max : 100, 31 | minorTicks: 5, 32 | decimal: decimalc 33 | }; 34 | 35 | var range = config.max - config.min; 36 | config.yellowZones = [{ 37 | from: config.min + range * 0.75, 38 | to: config.min + range * 0.9 39 | }]; 40 | config.redZones = [{ 41 | from: config.min + range * 0.9, 42 | to: config.max 43 | }]; 44 | 45 | var g = new Gauge(container, config); 46 | g.render(); 47 | return g; 48 | }; 49 | 50 | GaugesChart.prototype.draw = function() { 51 | this.gauges.bw.redraw(); 52 | this.gauges.requests.redraw(); 53 | this.gauges.errors.redraw(); 54 | }; 55 | 56 | GaugesChart.prototype.appendData = function(data) { 57 | this.formatData(data); 58 | this.draw(); 59 | }; 60 | 61 | GaugesChart.prototype.formatData = function(data) { 62 | var counter = 0; 63 | var totalBW = 0; 64 | var errors = 0; 65 | 66 | for (var i = 0; i < data.length; i++) { 67 | var req = data[i]; 68 | 69 | if (req.body_bytes_sent) { 70 | totalBW += req.body_bytes_sent; 71 | } 72 | 73 | if (!req.status || req.status >= 400) { 74 | errors++; 75 | } 76 | } 77 | 78 | totalBW = totalBW / 125000; 79 | errors = parseInt((errors / data.length) * 100); 80 | 81 | if (isNaN(errors)) { 82 | errors = 0; 83 | } 84 | 85 | this.gauges.errors.data = errors; 86 | 87 | if (this.gauges.bw.config.max < totalBW) { 88 | this.gauges.bw = this.createGauge('bwGauge', 'MBps', 0, parseInt(totalBW) + 1, true); 89 | } 90 | this.gauges.bw.data = totalBW; 91 | 92 | if (this.gauges.requests.config.max < data.length) { 93 | this.gauges.requests = this.createGauge('requestsGauge', 'Req/s', 0, data.length); 94 | } 95 | this.gauges.requests.data = data.length; 96 | }; 97 | -------------------------------------------------------------------------------- /static/js/loadChart.js: -------------------------------------------------------------------------------- 1 | var LoadChart = function(placeholder, opts) { 2 | this.graphSize = 150; 3 | this.interval = 1000; 4 | this.placeholder = placeholder; 5 | 6 | if (opts) { 7 | this.graphSize = opts.size || this.graphSize; 8 | this.interval = opts.interval || this.interval; 9 | } 10 | 11 | this.points = [{ 12 | 'name': 'Load %', 13 | 'color': '#FEE169', 14 | 'data': [] 15 | }, { 16 | 'name': 'Mem %', 17 | 'color': '#F9722E', 18 | 'data': [] 19 | }]; 20 | }; 21 | 22 | LoadChart.prototype.init = function() { 23 | var self = this; 24 | 25 | this.graph = new Rickshaw.Graph({ 26 | element: document.getElementById(self.placeholder), 27 | renderer: 'line', 28 | series: self.points, 29 | max: 100 30 | }); 31 | 32 | this.xAxis = new Rickshaw.Graph.Axis.Time({ 33 | graph: self.graph, 34 | ticksTreatment: 'glow' 35 | }); 36 | 37 | this.yAxis = new Rickshaw.Graph.Axis.Y({ 38 | graph: self.graph, 39 | tickFormat: function(y) { 40 | var abs_y = Math.abs(y); 41 | if (abs_y === 0) { 42 | return ''; 43 | } else { 44 | return y; 45 | } 46 | }, 47 | ticks: 5, 48 | ticksTreatment: 'glow' 49 | }); 50 | 51 | var legend = new Rickshaw.Graph.Legend({ 52 | graph: self.graph, 53 | element: document.getElementById(self.placeholder + 'Legend') 54 | }); 55 | 56 | var hoverDetail = new Rickshaw.Graph.HoverDetail({ 57 | graph: self.graph, 58 | xFormatter: function(x) { 59 | return new Date(x * 1000).toString(); 60 | } 61 | }); 62 | }; 63 | 64 | LoadChart.prototype.draw = function() { 65 | var self = this; 66 | this.graph.configure({ 67 | width: $('#' + self.placeholder).width(), 68 | height: $('#' + self.placeholder).height() 69 | }); 70 | this.graph.render(); 71 | this.xAxis.render(); 72 | this.yAxis.render(); 73 | }; 74 | 75 | LoadChart.prototype.appendData = function(info) { 76 | var d = new Date(); 77 | 78 | var memperc = (info.freemem / info.totalmem) * 100; 79 | var loadperc = info.loadpercentage; 80 | 81 | this.points[0].data.push({ 82 | 'x': parseInt(d.getTime() / 1000), 83 | 'y': loadperc 84 | }); 85 | 86 | if (this.points[0].data.length > this.graphSize) { 87 | this.points[0].data.shift(); 88 | } 89 | 90 | this.points[1].data.push({ 91 | 'x': parseInt(d.getTime() / 1000), 92 | 'y': memperc 93 | }); 94 | 95 | if (this.points[1].data.length > this.graphSize) { 96 | this.points[1].data.shift(); 97 | } 98 | 99 | this.draw(); 100 | }; 101 | 102 | LoadChart.prototype.formatData = function() {}; 103 | -------------------------------------------------------------------------------- /static/js/main.js: -------------------------------------------------------------------------------- 1 | var maxPoints = 150; 2 | 3 | $(window).resize(function() { 4 | location.reload(); 5 | }); 6 | 7 | $(document).ready(function() { 8 | var h = window.innerHeight; 9 | var w = window.innerWidth; 10 | 11 | h = h - $('#footer').height(); 12 | 13 | var rw = h / $('#content2').children().length; 14 | var aux = w * 0.15; 15 | if (rw > aux) { 16 | rw = aux; 17 | } 18 | rw++; 19 | 20 | $('#content1').css('width', w - rw - 20); 21 | $('#content2').css('width', rw); 22 | 23 | $('#container1').css('height', h / 4); 24 | $('#container2').css('height', h / 4); 25 | $('#container3').css('height', h / 4); 26 | $('#container4').css('height', h / 4); 27 | 28 | init(rw * 1.1); 29 | }); 30 | 31 | function init(rw) { 32 | var socket = io(); 33 | var data = []; 34 | 35 | var codeChart = new CodeChart('placeHolder1', { 36 | 'size': maxPoints 37 | }); 38 | var verbChart = new VerbChart('placeHolder2', { 39 | 'size': maxPoints 40 | }); 41 | var bandwidthChart = new BandwidthChart('placeHolder3', { 42 | 'size': maxPoints 43 | }); 44 | var loadChart = new LoadChart('placeHolder4', { 45 | 'size': maxPoints 46 | }); 47 | var gaugesChart = new GaugesChart({ 48 | 'size': rw 49 | }); 50 | codeChart.init(); 51 | verbChart.init(); 52 | bandwidthChart.init(); 53 | gaugesChart.init(); 54 | loadChart.init(); 55 | 56 | socket.on('data', function(data) { 57 | codeChart.appendData(data.requests); 58 | verbChart.appendData(data.requests); 59 | bandwidthChart.appendData(data.requests); 60 | gaugesChart.appendData(data.requests); 61 | 62 | 63 | if (gaugesChart.gauges.load.config.max < data.info.load[0]) { 64 | gaugesChart.gauges.load = gaugesChart.createGauge('loadGauge', 'Load', 0, parseInt(data.info.load[0]) + 1, true); 65 | } 66 | gaugesChart.gauges.load.data = data.info.load[0]; 67 | 68 | gaugesChart.gauges.mem.data = (data.info.freemem / data.info.totalmem) * 100; 69 | 70 | gaugesChart.gauges.load.redraw(); 71 | gaugesChart.gauges.mem.redraw(); 72 | 73 | loadChart.appendData(data.info); 74 | }); 75 | } 76 | -------------------------------------------------------------------------------- /static/js/verbChart.js: -------------------------------------------------------------------------------- 1 | var VerbChart = function(placeholder, opts) { 2 | this.graphSize = 150; 3 | this.interval = 1000; 4 | this.placeholder = placeholder; 5 | 6 | if (opts) { 7 | this.graphSize = opts.size || this.graphSize; 8 | this.interval = opts.interval || this.interval; 9 | } 10 | 11 | this.points = [{ 12 | 'name': 'GET', 13 | 'color': '#CDD452', 14 | 'data': [] 15 | }, { 16 | 'name': 'POST', 17 | 'color': '#FEE169', 18 | 'data': [] 19 | }, { 20 | 'name': 'OPTIONS', 21 | 'color': '#F9722E', 22 | 'data': [] 23 | }, { 24 | 'name': 'DELETE', 25 | 'color': '#C9313D', 26 | 'data': [] 27 | }, { 28 | 'name': 'other', 29 | 'color': '#68776C', 30 | 'data': [] 31 | }]; 32 | }; 33 | 34 | VerbChart.prototype.init = function() { 35 | var self = this; 36 | 37 | this.graph = new Rickshaw.Graph({ 38 | element: document.getElementById(self.placeholder), 39 | renderer: 'area', 40 | stroke: true, 41 | series: self.points 42 | }); 43 | 44 | this.xAxis = new Rickshaw.Graph.Axis.Time({ 45 | graph: self.graph, 46 | ticksTreatment: 'glow' 47 | }); 48 | 49 | this.yAxis = new Rickshaw.Graph.Axis.Y({ 50 | graph: self.graph, 51 | tickFormat: function(y) { 52 | var abs_y = Math.abs(y); 53 | if (abs_y === 0) { 54 | return ''; 55 | } else { 56 | return y; 57 | } 58 | }, 59 | ticks: 5, 60 | ticksTreatment: 'glow' 61 | }); 62 | 63 | var legend = new Rickshaw.Graph.Legend({ 64 | graph: self.graph, 65 | element: document.getElementById(self.placeholder + 'Legend') 66 | }); 67 | 68 | var hoverDetail = new Rickshaw.Graph.HoverDetail({ 69 | graph: self.graph, 70 | xFormatter: function(x) { 71 | return new Date(x * 1000).toString(); 72 | } 73 | }); 74 | 75 | var shelving = new Rickshaw.Graph.Behavior.Series.Toggle({ 76 | graph: self.graph, 77 | legend: legend 78 | }); 79 | 80 | var order = new Rickshaw.Graph.Behavior.Series.Order({ 81 | graph: self.graph, 82 | legend: legend 83 | }); 84 | 85 | var highlighter = new Rickshaw.Graph.Behavior.Series.Highlight({ 86 | graph: self.graph, 87 | legend: legend 88 | }); 89 | }; 90 | 91 | VerbChart.prototype.draw = function() { 92 | var self = this; 93 | this.graph.configure({ 94 | width: $('#' + self.placeholder).width(), 95 | height: $('#' + self.placeholder).height(), 96 | unstack: false 97 | }); 98 | this.graph.render(); 99 | this.xAxis.render(); 100 | this.yAxis.render(); 101 | }; 102 | 103 | VerbChart.prototype.appendData = function(data) { 104 | this.formatData(data); 105 | this.draw(); 106 | }; 107 | 108 | VerbChart.prototype.formatData = function(data) { 109 | var counter = 0; 110 | var d = new Date(); 111 | 112 | var codes = { 113 | 'GET': 0, 114 | 'POST': 0, 115 | 'OPTIONS': 0, 116 | 'DELETE': 0, 117 | 'other': 0 118 | }; 119 | 120 | for (var i = 0; i < data.length; i++) { 121 | var req = data[i]; 122 | var code = 'other'; 123 | 124 | if (req.http_method == 'GET') { 125 | codes.GET++; 126 | } else if (req.http_method == 'POST') { 127 | codes.POST++; 128 | } else if (req.http_method == 'OPTIONS') { 129 | codes.OPTIONS++; 130 | } else if (req.http_method == 'DELETE') { 131 | codes.DELETE++; 132 | } else { 133 | codes.other++; 134 | } 135 | } 136 | 137 | for (var property in codes) { 138 | if (codes.hasOwnProperty(property)) { 139 | 140 | this.points[counter].data.push({ 141 | 'x': parseInt(d.getTime() / 1000), 142 | 'y': codes[property] 143 | }); 144 | 145 | if (this.points[counter].data.length > this.graphSize) { 146 | this.points[counter].data.shift(); 147 | } 148 | counter++; 149 | } 150 | } 151 | }; 152 | -------------------------------------------------------------------------------- /static/vendors/Rickshaw.Graph.Annotate.js: -------------------------------------------------------------------------------- 1 | Rickshaw.namespace('Rickshaw.Graph.Annotate'); 2 | 3 | Rickshaw.Graph.Annotate = function(args) { 4 | 5 | var graph = this.graph = args.graph; 6 | this.elements = { timeline: args.element }; 7 | 8 | var self = this; 9 | 10 | this.data = {}; 11 | 12 | this.elements.timeline.classList.add('rickshaw_annotation_timeline'); 13 | 14 | this.add = function(time, content, end_time) { 15 | self.data[time] = self.data[time] || {'boxes': []}; 16 | self.data[time].boxes.push({content: content, end: end_time}); 17 | }; 18 | 19 | this.update = function() { 20 | 21 | Rickshaw.keys(self.data).forEach( function(time) { 22 | 23 | var annotation = self.data[time]; 24 | var left = self.graph.x(time); 25 | 26 | if (left < 0 || left > self.graph.x.range()[1]) { 27 | if (annotation.element) { 28 | annotation.line.classList.add('offscreen'); 29 | annotation.element.style.display = 'none'; 30 | } 31 | 32 | annotation.boxes.forEach( function(box) { 33 | if ( box.rangeElement ) box.rangeElement.classList.add('offscreen'); 34 | }); 35 | 36 | return; 37 | } 38 | 39 | if (!annotation.element) { 40 | var element = annotation.element = document.createElement('div'); 41 | element.classList.add('annotation'); 42 | this.elements.timeline.appendChild(element); 43 | element.addEventListener('click', function(e) { 44 | element.classList.toggle('active'); 45 | annotation.line.classList.toggle('active'); 46 | annotation.boxes.forEach( function(box) { 47 | if ( box.rangeElement ) box.rangeElement.classList.toggle('active'); 48 | }); 49 | }, false); 50 | 51 | } 52 | 53 | annotation.element.style.left = left + 'px'; 54 | annotation.element.style.display = 'block'; 55 | 56 | annotation.boxes.forEach( function(box) { 57 | 58 | 59 | var element = box.element; 60 | 61 | if (!element) { 62 | element = box.element = document.createElement('div'); 63 | element.classList.add('content'); 64 | element.innerHTML = box.content; 65 | annotation.element.appendChild(element); 66 | 67 | annotation.line = document.createElement('div'); 68 | annotation.line.classList.add('annotation_line'); 69 | self.graph.element.appendChild(annotation.line); 70 | 71 | if ( box.end ) { 72 | box.rangeElement = document.createElement('div'); 73 | box.rangeElement.classList.add('annotation_range'); 74 | self.graph.element.appendChild(box.rangeElement); 75 | } 76 | 77 | } 78 | 79 | if ( box.end ) { 80 | 81 | var annotationRangeStart = left; 82 | var annotationRangeEnd = Math.min( self.graph.x(box.end), self.graph.x.range()[1] ); 83 | 84 | // annotation makes more sense at end 85 | if ( annotationRangeStart > annotationRangeEnd ) { 86 | annotationRangeEnd = left; 87 | annotationRangeStart = Math.max( self.graph.x(box.end), self.graph.x.range()[0] ); 88 | } 89 | 90 | var annotationRangeWidth = annotationRangeEnd - annotationRangeStart; 91 | 92 | box.rangeElement.style.left = annotationRangeStart + 'px'; 93 | box.rangeElement.style.width = annotationRangeWidth + 'px'; 94 | 95 | box.rangeElement.classList.remove('offscreen'); 96 | } 97 | 98 | annotation.line.classList.remove('offscreen'); 99 | annotation.line.style.left = left + 'px'; 100 | } ); 101 | }, this ); 102 | }; 103 | 104 | this.graph.onUpdate( function() { self.update() } ); 105 | }; 106 | -------------------------------------------------------------------------------- /static/vendors/rickshaw.js: -------------------------------------------------------------------------------- 1 | (function (root, factory) { 2 | if (typeof define === 'function' && define.amd) { 3 | define(['d3'], function (d3) { 4 | return (root.Rickshaw = factory(d3)); 5 | }); 6 | } else if (typeof exports === 'object') { 7 | module.exports = factory(require('d3')); 8 | } else { 9 | root.Rickshaw = factory(d3); 10 | } 11 | }(this, function (d3) { 12 | /* jshint -W079 */ 13 | 14 | var Rickshaw = { 15 | 16 | namespace: function(namespace, obj) { 17 | 18 | var parts = namespace.split('.'); 19 | 20 | var parent = Rickshaw; 21 | 22 | for(var i = 1, length = parts.length; i < length; i++) { 23 | var currentPart = parts[i]; 24 | parent[currentPart] = parent[currentPart] || {}; 25 | parent = parent[currentPart]; 26 | } 27 | return parent; 28 | }, 29 | 30 | keys: function(obj) { 31 | var keys = []; 32 | for (var key in obj) keys.push(key); 33 | return keys; 34 | }, 35 | 36 | extend: function(destination, source) { 37 | 38 | for (var property in source) { 39 | destination[property] = source[property]; 40 | } 41 | return destination; 42 | }, 43 | 44 | clone: function(obj) { 45 | return JSON.parse(JSON.stringify(obj)); 46 | } 47 | }; 48 | /* Adapted from https://github.com/Jakobo/PTClass */ 49 | 50 | /* 51 | Copyright (c) 2005-2010 Sam Stephenson 52 | 53 | Permission is hereby granted, free of charge, to any person obtaining a copy 54 | of this software and associated documentation files (the "Software"), to deal 55 | in the Software without restriction, including without limitation the rights 56 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 57 | copies of the Software, and to permit persons to whom the Software is 58 | furnished to do so, subject to the following conditions: 59 | 60 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 61 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 62 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 63 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 64 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 65 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 66 | SOFTWARE. 67 | */ 68 | /* Based on Alex Arnell's inheritance implementation. */ 69 | /** section: Language 70 | * class Class 71 | * 72 | * Manages Prototype's class-based OOP system. 73 | * 74 | * Refer to Prototype's web site for a [tutorial on classes and 75 | * inheritance](http://prototypejs.org/learn/class-inheritance). 76 | **/ 77 | (function(globalContext) { 78 | /* ------------------------------------ */ 79 | /* Import from object.js */ 80 | /* ------------------------------------ */ 81 | var _toString = Object.prototype.toString, 82 | NULL_TYPE = 'Null', 83 | UNDEFINED_TYPE = 'Undefined', 84 | BOOLEAN_TYPE = 'Boolean', 85 | NUMBER_TYPE = 'Number', 86 | STRING_TYPE = 'String', 87 | OBJECT_TYPE = 'Object', 88 | FUNCTION_CLASS = '[object Function]'; 89 | function isFunction(object) { 90 | return _toString.call(object) === FUNCTION_CLASS; 91 | } 92 | function extend(destination, source) { 93 | for (var property in source) if (source.hasOwnProperty(property)) // modify protect primitive slaughter 94 | destination[property] = source[property]; 95 | return destination; 96 | } 97 | function keys(object) { 98 | if (Type(object) !== OBJECT_TYPE) { throw new TypeError(); } 99 | var results = []; 100 | for (var property in object) { 101 | if (object.hasOwnProperty(property)) { 102 | results.push(property); 103 | } 104 | } 105 | return results; 106 | } 107 | function Type(o) { 108 | switch(o) { 109 | case null: return NULL_TYPE; 110 | case (void 0): return UNDEFINED_TYPE; 111 | } 112 | var type = typeof o; 113 | switch(type) { 114 | case 'boolean': return BOOLEAN_TYPE; 115 | case 'number': return NUMBER_TYPE; 116 | case 'string': return STRING_TYPE; 117 | } 118 | return OBJECT_TYPE; 119 | } 120 | function isUndefined(object) { 121 | return typeof object === "undefined"; 122 | } 123 | /* ------------------------------------ */ 124 | /* Import from Function.js */ 125 | /* ------------------------------------ */ 126 | var slice = Array.prototype.slice; 127 | function argumentNames(fn) { 128 | var names = fn.toString().match(/^[\s\(]*function[^(]*\(([^)]*)\)/)[1] 129 | .replace(/\/\/.*?[\r\n]|\/\*(?:.|[\r\n])*?\*\//g, '') 130 | .replace(/\s+/g, '').split(','); 131 | return names.length == 1 && !names[0] ? [] : names; 132 | } 133 | function wrap(fn, wrapper) { 134 | var __method = fn; 135 | return function() { 136 | var a = update([bind(__method, this)], arguments); 137 | return wrapper.apply(this, a); 138 | } 139 | } 140 | function update(array, args) { 141 | var arrayLength = array.length, length = args.length; 142 | while (length--) array[arrayLength + length] = args[length]; 143 | return array; 144 | } 145 | function merge(array, args) { 146 | array = slice.call(array, 0); 147 | return update(array, args); 148 | } 149 | function bind(fn, context) { 150 | if (arguments.length < 2 && isUndefined(arguments[0])) return this; 151 | var __method = fn, args = slice.call(arguments, 2); 152 | return function() { 153 | var a = merge(args, arguments); 154 | return __method.apply(context, a); 155 | } 156 | } 157 | 158 | /* ------------------------------------ */ 159 | /* Import from Prototype.js */ 160 | /* ------------------------------------ */ 161 | var emptyFunction = function(){}; 162 | 163 | var Class = (function() { 164 | 165 | // Some versions of JScript fail to enumerate over properties, names of which 166 | // correspond to non-enumerable properties in the prototype chain 167 | var IS_DONTENUM_BUGGY = (function(){ 168 | for (var p in { toString: 1 }) { 169 | // check actual property name, so that it works with augmented Object.prototype 170 | if (p === 'toString') return false; 171 | } 172 | return true; 173 | })(); 174 | 175 | function subclass() {}; 176 | function create() { 177 | var parent = null, properties = [].slice.apply(arguments); 178 | if (isFunction(properties[0])) 179 | parent = properties.shift(); 180 | 181 | function klass() { 182 | this.initialize.apply(this, arguments); 183 | } 184 | 185 | extend(klass, Class.Methods); 186 | klass.superclass = parent; 187 | klass.subclasses = []; 188 | 189 | if (parent) { 190 | subclass.prototype = parent.prototype; 191 | klass.prototype = new subclass; 192 | try { parent.subclasses.push(klass) } catch(e) {} 193 | } 194 | 195 | for (var i = 0, length = properties.length; i < length; i++) 196 | klass.addMethods(properties[i]); 197 | 198 | if (!klass.prototype.initialize) 199 | klass.prototype.initialize = emptyFunction; 200 | 201 | klass.prototype.constructor = klass; 202 | return klass; 203 | } 204 | 205 | function addMethods(source) { 206 | var ancestor = this.superclass && this.superclass.prototype, 207 | properties = keys(source); 208 | 209 | // IE6 doesn't enumerate `toString` and `valueOf` (among other built-in `Object.prototype`) properties, 210 | // Force copy if they're not Object.prototype ones. 211 | // Do not copy other Object.prototype.* for performance reasons 212 | if (IS_DONTENUM_BUGGY) { 213 | if (source.toString != Object.prototype.toString) 214 | properties.push("toString"); 215 | if (source.valueOf != Object.prototype.valueOf) 216 | properties.push("valueOf"); 217 | } 218 | 219 | for (var i = 0, length = properties.length; i < length; i++) { 220 | var property = properties[i], value = source[property]; 221 | if (ancestor && isFunction(value) && 222 | argumentNames(value)[0] == "$super") { 223 | var method = value; 224 | value = wrap((function(m) { 225 | return function() { return ancestor[m].apply(this, arguments); }; 226 | })(property), method); 227 | 228 | value.valueOf = bind(method.valueOf, method); 229 | value.toString = bind(method.toString, method); 230 | } 231 | this.prototype[property] = value; 232 | } 233 | 234 | return this; 235 | } 236 | 237 | return { 238 | create: create, 239 | Methods: { 240 | addMethods: addMethods 241 | } 242 | }; 243 | })(); 244 | 245 | if (globalContext.exports) { 246 | globalContext.exports.Class = Class; 247 | } 248 | else { 249 | globalContext.Class = Class; 250 | } 251 | })(Rickshaw); 252 | Rickshaw.namespace('Rickshaw.Compat.ClassList'); 253 | 254 | Rickshaw.Compat.ClassList = function() { 255 | 256 | /* adapted from http://purl.eligrey.com/github/classList.js/blob/master/classList.js */ 257 | 258 | if (typeof document !== "undefined" && !("classList" in document.createElement("a"))) { 259 | 260 | (function (view) { 261 | 262 | "use strict"; 263 | 264 | var 265 | classListProp = "classList" 266 | , protoProp = "prototype" 267 | , elemCtrProto = (view.HTMLElement || view.Element)[protoProp] 268 | , objCtr = Object 269 | , strTrim = String[protoProp].trim || function () { 270 | return this.replace(/^\s+|\s+$/g, ""); 271 | } 272 | , arrIndexOf = Array[protoProp].indexOf || function (item) { 273 | var 274 | i = 0 275 | , len = this.length 276 | ; 277 | for (; i < len; i++) { 278 | if (i in this && this[i] === item) { 279 | return i; 280 | } 281 | } 282 | return -1; 283 | } 284 | // Vendors: please allow content code to instantiate DOMExceptions 285 | , DOMEx = function (type, message) { 286 | this.name = type; 287 | this.code = DOMException[type]; 288 | this.message = message; 289 | } 290 | , checkTokenAndGetIndex = function (classList, token) { 291 | if (token === "") { 292 | throw new DOMEx( 293 | "SYNTAX_ERR" 294 | , "An invalid or illegal string was specified" 295 | ); 296 | } 297 | if (/\s/.test(token)) { 298 | throw new DOMEx( 299 | "INVALID_CHARACTER_ERR" 300 | , "String contains an invalid character" 301 | ); 302 | } 303 | return arrIndexOf.call(classList, token); 304 | } 305 | , ClassList = function (elem) { 306 | var 307 | trimmedClasses = strTrim.call(elem.className) 308 | , classes = trimmedClasses ? trimmedClasses.split(/\s+/) : [] 309 | , i = 0 310 | , len = classes.length 311 | ; 312 | for (; i < len; i++) { 313 | this.push(classes[i]); 314 | } 315 | this._updateClassName = function () { 316 | elem.className = this.toString(); 317 | }; 318 | } 319 | , classListProto = ClassList[protoProp] = [] 320 | , classListGetter = function () { 321 | return new ClassList(this); 322 | } 323 | ; 324 | // Most DOMException implementations don't allow calling DOMException's toString() 325 | // on non-DOMExceptions. Error's toString() is sufficient here. 326 | DOMEx[protoProp] = Error[protoProp]; 327 | classListProto.item = function (i) { 328 | return this[i] || null; 329 | }; 330 | classListProto.contains = function (token) { 331 | token += ""; 332 | return checkTokenAndGetIndex(this, token) !== -1; 333 | }; 334 | classListProto.add = function (token) { 335 | token += ""; 336 | if (checkTokenAndGetIndex(this, token) === -1) { 337 | this.push(token); 338 | this._updateClassName(); 339 | } 340 | }; 341 | classListProto.remove = function (token) { 342 | token += ""; 343 | var index = checkTokenAndGetIndex(this, token); 344 | if (index !== -1) { 345 | this.splice(index, 1); 346 | this._updateClassName(); 347 | } 348 | }; 349 | classListProto.toggle = function (token) { 350 | token += ""; 351 | if (checkTokenAndGetIndex(this, token) === -1) { 352 | this.add(token); 353 | } else { 354 | this.remove(token); 355 | } 356 | }; 357 | classListProto.toString = function () { 358 | return this.join(" "); 359 | }; 360 | 361 | if (objCtr.defineProperty) { 362 | var classListPropDesc = { 363 | get: classListGetter 364 | , enumerable: true 365 | , configurable: true 366 | }; 367 | try { 368 | objCtr.defineProperty(elemCtrProto, classListProp, classListPropDesc); 369 | } catch (ex) { // IE 8 doesn't support enumerable:true 370 | if (ex.number === -0x7FF5EC54) { 371 | classListPropDesc.enumerable = false; 372 | objCtr.defineProperty(elemCtrProto, classListProp, classListPropDesc); 373 | } 374 | } 375 | } else if (objCtr[protoProp].__defineGetter__) { 376 | elemCtrProto.__defineGetter__(classListProp, classListGetter); 377 | } 378 | 379 | }(window)); 380 | 381 | } 382 | }; 383 | 384 | if ( (typeof RICKSHAW_NO_COMPAT !== "undefined" && !RICKSHAW_NO_COMPAT) || typeof RICKSHAW_NO_COMPAT === "undefined") { 385 | new Rickshaw.Compat.ClassList(); 386 | } 387 | Rickshaw.namespace('Rickshaw.Graph'); 388 | 389 | Rickshaw.Graph = function(args) { 390 | 391 | var self = this; 392 | 393 | this.initialize = function(args) { 394 | 395 | if (!args.element) throw "Rickshaw.Graph needs a reference to an element"; 396 | if (args.element.nodeType !== 1) throw "Rickshaw.Graph element was defined but not an HTML element"; 397 | 398 | this.element = args.element; 399 | this.series = args.series; 400 | this.window = {}; 401 | 402 | this.updateCallbacks = []; 403 | this.configureCallbacks = []; 404 | 405 | this.defaults = { 406 | interpolation: 'cardinal', 407 | offset: 'zero', 408 | min: undefined, 409 | max: undefined, 410 | preserve: false, 411 | xScale: undefined, 412 | yScale: undefined, 413 | stack: true 414 | }; 415 | 416 | this._loadRenderers(); 417 | this.configure(args); 418 | this.validateSeries(args.series); 419 | 420 | this.series.active = function() { return self.series.filter( function(s) { return !s.disabled } ) }; 421 | this.setSize({ width: args.width, height: args.height }); 422 | this.element.classList.add('rickshaw_graph'); 423 | 424 | this.vis = d3.select(this.element) 425 | .append("svg:svg") 426 | .attr('width', this.width) 427 | .attr('height', this.height); 428 | 429 | this.discoverRange(); 430 | }; 431 | 432 | this._loadRenderers = function() { 433 | 434 | for (var name in Rickshaw.Graph.Renderer) { 435 | if (!name || !Rickshaw.Graph.Renderer.hasOwnProperty(name)) continue; 436 | var r = Rickshaw.Graph.Renderer[name]; 437 | if (!r || !r.prototype || !r.prototype.render) continue; 438 | self.registerRenderer(new r( { graph: self } )); 439 | } 440 | }; 441 | 442 | this.validateSeries = function(series) { 443 | 444 | if (!Array.isArray(series) && !(series instanceof Rickshaw.Series)) { 445 | var seriesSignature = Object.prototype.toString.apply(series); 446 | throw "series is not an array: " + seriesSignature; 447 | } 448 | 449 | var pointsCount; 450 | 451 | series.forEach( function(s) { 452 | 453 | if (!(s instanceof Object)) { 454 | throw "series element is not an object: " + s; 455 | } 456 | if (!(s.data)) { 457 | throw "series has no data: " + JSON.stringify(s); 458 | } 459 | if (!Array.isArray(s.data)) { 460 | throw "series data is not an array: " + JSON.stringify(s.data); 461 | } 462 | 463 | if (s.data.length > 0) { 464 | var x = s.data[0].x; 465 | var y = s.data[0].y; 466 | 467 | if (typeof x != 'number' || ( typeof y != 'number' && y !== null ) ) { 468 | throw "x and y properties of points should be numbers instead of " + 469 | (typeof x) + " and " + (typeof y); 470 | } 471 | } 472 | 473 | if (s.data.length >= 3) { 474 | // probe to sanity check sort order 475 | if (s.data[2].x < s.data[1].x || s.data[1].x < s.data[0].x || s.data[s.data.length - 1].x < s.data[0].x) { 476 | throw "series data needs to be sorted on x values for series name: " + s.name; 477 | } 478 | } 479 | 480 | }, this ); 481 | }; 482 | 483 | this.dataDomain = function() { 484 | 485 | var data = this.series.map( function(s) { return s.data } ); 486 | 487 | var min = d3.min( data.map( function(d) { return d[0].x } ) ); 488 | var max = d3.max( data.map( function(d) { return d[d.length - 1].x } ) ); 489 | 490 | return [min, max]; 491 | }; 492 | 493 | this.discoverRange = function() { 494 | 495 | var domain = this.renderer.domain(); 496 | 497 | // this.*Scale is coming from the configuration dictionary 498 | // which may be referenced by the Graph creator, or shared 499 | // with other Graphs. We need to ensure we copy the scale 500 | // so that our mutations do not change the object given to us. 501 | // Hence the .copy() 502 | this.x = (this.xScale || d3.scale.linear()).copy().domain(domain.x).range([0, this.width]); 503 | this.y = (this.yScale || d3.scale.linear()).copy().domain(domain.y).range([this.height, 0]); 504 | 505 | this.x.magnitude = d3.scale.linear() 506 | .domain([domain.x[0] - domain.x[0], domain.x[1] - domain.x[0]]) 507 | .range([0, this.width]); 508 | 509 | this.y.magnitude = d3.scale.linear() 510 | .domain([domain.y[0] - domain.y[0], domain.y[1] - domain.y[0]]) 511 | .range([0, this.height]); 512 | }; 513 | 514 | this.render = function() { 515 | 516 | var stackedData = this.stackData(); 517 | this.discoverRange(); 518 | 519 | this.renderer.render(); 520 | 521 | this.updateCallbacks.forEach( function(callback) { 522 | callback(); 523 | } ); 524 | 525 | }; 526 | 527 | this.update = this.render; 528 | 529 | this.stackData = function() { 530 | 531 | var data = this.series.active() 532 | .map( function(d) { return d.data } ) 533 | .map( function(d) { return d.filter( function(d) { return this._slice(d) }, this ) }, this); 534 | 535 | var preserve = this.preserve; 536 | if (!preserve) { 537 | this.series.forEach( function(series) { 538 | if (series.scale) { 539 | // data must be preserved when a scale is used 540 | preserve = true; 541 | } 542 | } ); 543 | } 544 | 545 | data = preserve ? Rickshaw.clone(data) : data; 546 | 547 | this.series.active().forEach( function(series, index) { 548 | if (series.scale) { 549 | // apply scale to each series 550 | var seriesData = data[index]; 551 | if(seriesData) { 552 | seriesData.forEach( function(d) { 553 | d.y = series.scale(d.y); 554 | } ); 555 | } 556 | } 557 | } ); 558 | 559 | this.stackData.hooks.data.forEach( function(entry) { 560 | data = entry.f.apply(self, [data]); 561 | } ); 562 | 563 | var stackedData; 564 | 565 | if (!this.renderer.unstack) { 566 | 567 | this._validateStackable(); 568 | 569 | var layout = d3.layout.stack(); 570 | layout.offset( self.offset ); 571 | stackedData = layout(data); 572 | } 573 | 574 | stackedData = stackedData || data; 575 | 576 | if (this.renderer.unstack) { 577 | stackedData.forEach( function(seriesData) { 578 | seriesData.forEach( function(d) { 579 | d.y0 = d.y0 === undefined ? 0 : d.y0; 580 | } ); 581 | } ); 582 | } 583 | 584 | this.stackData.hooks.after.forEach( function(entry) { 585 | stackedData = entry.f.apply(self, [data]); 586 | } ); 587 | 588 | var i = 0; 589 | this.series.forEach( function(series) { 590 | if (series.disabled) return; 591 | series.stack = stackedData[i++]; 592 | } ); 593 | 594 | this.stackedData = stackedData; 595 | return stackedData; 596 | }; 597 | 598 | this._validateStackable = function() { 599 | 600 | var series = this.series; 601 | var pointsCount; 602 | 603 | series.forEach( function(s) { 604 | 605 | pointsCount = pointsCount || s.data.length; 606 | 607 | if (pointsCount && s.data.length != pointsCount) { 608 | throw "stacked series cannot have differing numbers of points: " + 609 | pointsCount + " vs " + s.data.length + "; see Rickshaw.Series.fill()"; 610 | } 611 | 612 | }, this ); 613 | }; 614 | 615 | this.stackData.hooks = { data: [], after: [] }; 616 | 617 | this._slice = function(d) { 618 | 619 | if (this.window.xMin || this.window.xMax) { 620 | 621 | var isInRange = true; 622 | 623 | if (this.window.xMin && d.x < this.window.xMin) isInRange = false; 624 | if (this.window.xMax && d.x > this.window.xMax) isInRange = false; 625 | 626 | return isInRange; 627 | } 628 | 629 | return true; 630 | }; 631 | 632 | this.onUpdate = function(callback) { 633 | this.updateCallbacks.push(callback); 634 | }; 635 | 636 | this.onConfigure = function(callback) { 637 | this.configureCallbacks.push(callback); 638 | }; 639 | 640 | this.registerRenderer = function(renderer) { 641 | this._renderers = this._renderers || {}; 642 | this._renderers[renderer.name] = renderer; 643 | }; 644 | 645 | this.configure = function(args) { 646 | 647 | this.config = this.config || {}; 648 | 649 | if (args.width || args.height) { 650 | this.setSize(args); 651 | } 652 | 653 | Rickshaw.keys(this.defaults).forEach( function(k) { 654 | this.config[k] = k in args ? args[k] 655 | : k in this ? this[k] 656 | : this.defaults[k]; 657 | }, this ); 658 | 659 | Rickshaw.keys(this.config).forEach( function(k) { 660 | this[k] = this.config[k]; 661 | }, this ); 662 | 663 | if ('stack' in args) args.unstack = !args.stack; 664 | 665 | var renderer = args.renderer || (this.renderer && this.renderer.name) || 'stack'; 666 | this.setRenderer(renderer, args); 667 | 668 | this.configureCallbacks.forEach( function(callback) { 669 | callback(args); 670 | } ); 671 | }; 672 | 673 | this.setRenderer = function(r, args) { 674 | if (typeof r == 'function') { 675 | this.renderer = new r( { graph: self } ); 676 | this.registerRenderer(this.renderer); 677 | } else { 678 | if (!this._renderers[r]) { 679 | throw "couldn't find renderer " + r; 680 | } 681 | this.renderer = this._renderers[r]; 682 | } 683 | 684 | if (typeof args == 'object') { 685 | this.renderer.configure(args); 686 | } 687 | }; 688 | 689 | this.setSize = function(args) { 690 | 691 | args = args || {}; 692 | 693 | if (typeof window !== undefined) { 694 | var style = window.getComputedStyle(this.element, null); 695 | var elementWidth = parseInt(style.getPropertyValue('width'), 10); 696 | var elementHeight = parseInt(style.getPropertyValue('height'), 10); 697 | } 698 | 699 | this.width = args.width || elementWidth || 400; 700 | this.height = args.height || elementHeight || 250; 701 | 702 | this.vis && this.vis 703 | .attr('width', this.width) 704 | .attr('height', this.height); 705 | }; 706 | 707 | this.initialize(args); 708 | }; 709 | Rickshaw.namespace('Rickshaw.Fixtures.Color'); 710 | 711 | Rickshaw.Fixtures.Color = function() { 712 | 713 | this.schemes = {}; 714 | 715 | this.schemes.spectrum14 = [ 716 | '#ecb796', 717 | '#dc8f70', 718 | '#b2a470', 719 | '#92875a', 720 | '#716c49', 721 | '#d2ed82', 722 | '#bbe468', 723 | '#a1d05d', 724 | '#e7cbe6', 725 | '#d8aad6', 726 | '#a888c2', 727 | '#9dc2d3', 728 | '#649eb9', 729 | '#387aa3' 730 | ].reverse(); 731 | 732 | this.schemes.spectrum2000 = [ 733 | '#57306f', 734 | '#514c76', 735 | '#646583', 736 | '#738394', 737 | '#6b9c7d', 738 | '#84b665', 739 | '#a7ca50', 740 | '#bfe746', 741 | '#e2f528', 742 | '#fff726', 743 | '#ecdd00', 744 | '#d4b11d', 745 | '#de8800', 746 | '#de4800', 747 | '#c91515', 748 | '#9a0000', 749 | '#7b0429', 750 | '#580839', 751 | '#31082b' 752 | ]; 753 | 754 | this.schemes.spectrum2001 = [ 755 | '#2f243f', 756 | '#3c2c55', 757 | '#4a3768', 758 | '#565270', 759 | '#6b6b7c', 760 | '#72957f', 761 | '#86ad6e', 762 | '#a1bc5e', 763 | '#b8d954', 764 | '#d3e04e', 765 | '#ccad2a', 766 | '#cc8412', 767 | '#c1521d', 768 | '#ad3821', 769 | '#8a1010', 770 | '#681717', 771 | '#531e1e', 772 | '#3d1818', 773 | '#320a1b' 774 | ]; 775 | 776 | this.schemes.classic9 = [ 777 | '#423d4f', 778 | '#4a6860', 779 | '#848f39', 780 | '#a2b73c', 781 | '#ddcb53', 782 | '#c5a32f', 783 | '#7d5836', 784 | '#963b20', 785 | '#7c2626', 786 | '#491d37', 787 | '#2f254a' 788 | ].reverse(); 789 | 790 | this.schemes.httpStatus = { 791 | 503: '#ea5029', 792 | 502: '#d23f14', 793 | 500: '#bf3613', 794 | 410: '#efacea', 795 | 409: '#e291dc', 796 | 403: '#f457e8', 797 | 408: '#e121d2', 798 | 401: '#b92dae', 799 | 405: '#f47ceb', 800 | 404: '#a82a9f', 801 | 400: '#b263c6', 802 | 301: '#6fa024', 803 | 302: '#87c32b', 804 | 307: '#a0d84c', 805 | 304: '#28b55c', 806 | 200: '#1a4f74', 807 | 206: '#27839f', 808 | 201: '#52adc9', 809 | 202: '#7c979f', 810 | 203: '#a5b8bd', 811 | 204: '#c1cdd1' 812 | }; 813 | 814 | this.schemes.colorwheel = [ 815 | '#b5b6a9', 816 | '#858772', 817 | '#785f43', 818 | '#96557e', 819 | '#4682b4', 820 | '#65b9ac', 821 | '#73c03a', 822 | '#cb513a' 823 | ].reverse(); 824 | 825 | this.schemes.cool = [ 826 | '#5e9d2f', 827 | '#73c03a', 828 | '#4682b4', 829 | '#7bc3b8', 830 | '#a9884e', 831 | '#c1b266', 832 | '#a47493', 833 | '#c09fb5' 834 | ]; 835 | 836 | this.schemes.munin = [ 837 | '#00cc00', 838 | '#0066b3', 839 | '#ff8000', 840 | '#ffcc00', 841 | '#330099', 842 | '#990099', 843 | '#ccff00', 844 | '#ff0000', 845 | '#808080', 846 | '#008f00', 847 | '#00487d', 848 | '#b35a00', 849 | '#b38f00', 850 | '#6b006b', 851 | '#8fb300', 852 | '#b30000', 853 | '#bebebe', 854 | '#80ff80', 855 | '#80c9ff', 856 | '#ffc080', 857 | '#ffe680', 858 | '#aa80ff', 859 | '#ee00cc', 860 | '#ff8080', 861 | '#666600', 862 | '#ffbfff', 863 | '#00ffcc', 864 | '#cc6699', 865 | '#999900' 866 | ]; 867 | }; 868 | Rickshaw.namespace('Rickshaw.Fixtures.RandomData'); 869 | 870 | Rickshaw.Fixtures.RandomData = function(timeInterval) { 871 | 872 | var addData; 873 | timeInterval = timeInterval || 1; 874 | 875 | var lastRandomValue = 200; 876 | 877 | var timeBase = Math.floor(new Date().getTime() / 1000); 878 | 879 | this.addData = function(data) { 880 | 881 | var randomValue = Math.random() * 100 + 15 + lastRandomValue; 882 | var index = data[0].length; 883 | 884 | var counter = 1; 885 | 886 | data.forEach( function(series) { 887 | var randomVariance = Math.random() * 20; 888 | var v = randomValue / 25 + counter++ + 889 | (Math.cos((index * counter * 11) / 960) + 2) * 15 + 890 | (Math.cos(index / 7) + 2) * 7 + 891 | (Math.cos(index / 17) + 2) * 1; 892 | 893 | series.push( { x: (index * timeInterval) + timeBase, y: v + randomVariance } ); 894 | } ); 895 | 896 | lastRandomValue = randomValue * 0.85; 897 | }; 898 | 899 | this.removeData = function(data) { 900 | data.forEach( function(series) { 901 | series.shift(); 902 | } ); 903 | timeBase += timeInterval; 904 | }; 905 | }; 906 | 907 | Rickshaw.namespace('Rickshaw.Fixtures.Time'); 908 | 909 | Rickshaw.Fixtures.Time = function() { 910 | 911 | var self = this; 912 | 913 | this.months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; 914 | 915 | this.units = [ 916 | { 917 | name: 'decade', 918 | seconds: 86400 * 365.25 * 10, 919 | formatter: function(d) { return (parseInt(d.getUTCFullYear() / 10, 10) * 10) } 920 | }, { 921 | name: 'year', 922 | seconds: 86400 * 365.25, 923 | formatter: function(d) { return d.getUTCFullYear() } 924 | }, { 925 | name: 'month', 926 | seconds: 86400 * 30.5, 927 | formatter: function(d) { return self.months[d.getUTCMonth()] } 928 | }, { 929 | name: 'week', 930 | seconds: 86400 * 7, 931 | formatter: function(d) { return self.formatDate(d) } 932 | }, { 933 | name: 'day', 934 | seconds: 86400, 935 | formatter: function(d) { return d.getUTCDate() } 936 | }, { 937 | name: '6 hour', 938 | seconds: 3600 * 6, 939 | formatter: function(d) { return self.formatTime(d) } 940 | }, { 941 | name: 'hour', 942 | seconds: 3600, 943 | formatter: function(d) { return self.formatTime(d) } 944 | }, { 945 | name: '15 minute', 946 | seconds: 60 * 15, 947 | formatter: function(d) { return self.formatTime(d) } 948 | }, { 949 | name: 'minute', 950 | seconds: 60, 951 | formatter: function(d) { return d.getUTCMinutes() } 952 | }, { 953 | name: '15 second', 954 | seconds: 15, 955 | formatter: function(d) { return d.getUTCSeconds() + 's' } 956 | }, { 957 | name: 'second', 958 | seconds: 1, 959 | formatter: function(d) { return d.getUTCSeconds() + 's' } 960 | }, { 961 | name: 'decisecond', 962 | seconds: 1/10, 963 | formatter: function(d) { return d.getUTCMilliseconds() + 'ms' } 964 | }, { 965 | name: 'centisecond', 966 | seconds: 1/100, 967 | formatter: function(d) { return d.getUTCMilliseconds() + 'ms' } 968 | } 969 | ]; 970 | 971 | this.unit = function(unitName) { 972 | return this.units.filter( function(unit) { return unitName == unit.name } ).shift(); 973 | }; 974 | 975 | this.formatDate = function(d) { 976 | return d3.time.format('%b %e')(d); 977 | }; 978 | 979 | this.formatTime = function(d) { 980 | return d.toUTCString().match(/(\d+:\d+):/)[1]; 981 | }; 982 | 983 | this.ceil = function(time, unit) { 984 | 985 | var date, floor, year; 986 | 987 | if (unit.name == 'month') { 988 | 989 | date = new Date(time * 1000); 990 | 991 | floor = Date.UTC(date.getUTCFullYear(), date.getUTCMonth()) / 1000; 992 | if (floor == time) return time; 993 | 994 | year = date.getUTCFullYear(); 995 | var month = date.getUTCMonth(); 996 | 997 | if (month == 11) { 998 | month = 0; 999 | year = year + 1; 1000 | } else { 1001 | month += 1; 1002 | } 1003 | 1004 | return Date.UTC(year, month) / 1000; 1005 | } 1006 | 1007 | if (unit.name == 'year') { 1008 | 1009 | date = new Date(time * 1000); 1010 | 1011 | floor = Date.UTC(date.getUTCFullYear(), 0) / 1000; 1012 | if (floor == time) return time; 1013 | 1014 | year = date.getUTCFullYear() + 1; 1015 | 1016 | return Date.UTC(year, 0) / 1000; 1017 | } 1018 | 1019 | return Math.ceil(time / unit.seconds) * unit.seconds; 1020 | }; 1021 | }; 1022 | Rickshaw.namespace('Rickshaw.Fixtures.Time.Local'); 1023 | 1024 | Rickshaw.Fixtures.Time.Local = function() { 1025 | 1026 | var self = this; 1027 | 1028 | this.months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; 1029 | 1030 | this.units = [ 1031 | { 1032 | name: 'decade', 1033 | seconds: 86400 * 365.25 * 10, 1034 | formatter: function(d) { return (parseInt(d.getFullYear() / 10, 10) * 10) } 1035 | }, { 1036 | name: 'year', 1037 | seconds: 86400 * 365.25, 1038 | formatter: function(d) { return d.getFullYear() } 1039 | }, { 1040 | name: 'month', 1041 | seconds: 86400 * 30.5, 1042 | formatter: function(d) { return self.months[d.getMonth()] } 1043 | }, { 1044 | name: 'week', 1045 | seconds: 86400 * 7, 1046 | formatter: function(d) { return self.formatDate(d) } 1047 | }, { 1048 | name: 'day', 1049 | seconds: 86400, 1050 | formatter: function(d) { return d.getDate() } 1051 | }, { 1052 | name: '6 hour', 1053 | seconds: 3600 * 6, 1054 | formatter: function(d) { return self.formatTime(d) } 1055 | }, { 1056 | name: 'hour', 1057 | seconds: 3600, 1058 | formatter: function(d) { return self.formatTime(d) } 1059 | }, { 1060 | name: '15 minute', 1061 | seconds: 60 * 15, 1062 | formatter: function(d) { return self.formatTime(d) } 1063 | }, { 1064 | name: 'minute', 1065 | seconds: 60, 1066 | formatter: function(d) { return d.getMinutes() } 1067 | }, { 1068 | name: '15 second', 1069 | seconds: 15, 1070 | formatter: function(d) { return d.getSeconds() + 's' } 1071 | }, { 1072 | name: 'second', 1073 | seconds: 1, 1074 | formatter: function(d) { return d.getSeconds() + 's' } 1075 | }, { 1076 | name: 'decisecond', 1077 | seconds: 1/10, 1078 | formatter: function(d) { return d.getMilliseconds() + 'ms' } 1079 | }, { 1080 | name: 'centisecond', 1081 | seconds: 1/100, 1082 | formatter: function(d) { return d.getMilliseconds() + 'ms' } 1083 | } 1084 | ]; 1085 | 1086 | this.unit = function(unitName) { 1087 | return this.units.filter( function(unit) { return unitName == unit.name } ).shift(); 1088 | }; 1089 | 1090 | this.formatDate = function(d) { 1091 | return d3.time.format('%b %e')(d); 1092 | }; 1093 | 1094 | this.formatTime = function(d) { 1095 | return d.toString().match(/(\d+:\d+):/)[1]; 1096 | }; 1097 | 1098 | this.ceil = function(time, unit) { 1099 | 1100 | var date, floor, year; 1101 | 1102 | if (unit.name == 'day') { 1103 | 1104 | var nearFuture = new Date((time + unit.seconds - 1) * 1000); 1105 | 1106 | var rounded = new Date(0); 1107 | rounded.setMilliseconds(0); 1108 | rounded.setSeconds(0); 1109 | rounded.setMinutes(0); 1110 | rounded.setHours(0); 1111 | rounded.setDate(nearFuture.getDate()); 1112 | rounded.setMonth(nearFuture.getMonth()); 1113 | rounded.setFullYear(nearFuture.getFullYear()); 1114 | 1115 | return rounded.getTime() / 1000; 1116 | } 1117 | 1118 | if (unit.name == 'month') { 1119 | 1120 | date = new Date(time * 1000); 1121 | 1122 | floor = new Date(date.getFullYear(), date.getMonth()).getTime() / 1000; 1123 | if (floor == time) return time; 1124 | 1125 | year = date.getFullYear(); 1126 | var month = date.getMonth(); 1127 | 1128 | if (month == 11) { 1129 | month = 0; 1130 | year = year + 1; 1131 | } else { 1132 | month += 1; 1133 | } 1134 | 1135 | return new Date(year, month).getTime() / 1000; 1136 | } 1137 | 1138 | if (unit.name == 'year') { 1139 | 1140 | date = new Date(time * 1000); 1141 | 1142 | floor = new Date(date.getUTCFullYear(), 0).getTime() / 1000; 1143 | if (floor == time) return time; 1144 | 1145 | year = date.getFullYear() + 1; 1146 | 1147 | return new Date(year, 0).getTime() / 1000; 1148 | } 1149 | 1150 | return Math.ceil(time / unit.seconds) * unit.seconds; 1151 | }; 1152 | }; 1153 | Rickshaw.namespace('Rickshaw.Fixtures.Number'); 1154 | 1155 | Rickshaw.Fixtures.Number.formatKMBT = function(y) { 1156 | var abs_y = Math.abs(y); 1157 | if (abs_y >= 1000000000000) { return y / 1000000000000 + "T" } 1158 | else if (abs_y >= 1000000000) { return y / 1000000000 + "B" } 1159 | else if (abs_y >= 1000000) { return y / 1000000 + "M" } 1160 | else if (abs_y >= 1000) { return y / 1000 + "K" } 1161 | else if (abs_y < 1 && y > 0) { return y.toFixed(2) } 1162 | else if (abs_y === 0) { return '' } 1163 | else { return y } 1164 | }; 1165 | 1166 | Rickshaw.Fixtures.Number.formatBase1024KMGTP = function(y) { 1167 | var abs_y = Math.abs(y); 1168 | if (abs_y >= 1125899906842624) { return y / 1125899906842624 + "P" } 1169 | else if (abs_y >= 1099511627776){ return y / 1099511627776 + "T" } 1170 | else if (abs_y >= 1073741824) { return y / 1073741824 + "G" } 1171 | else if (abs_y >= 1048576) { return y / 1048576 + "M" } 1172 | else if (abs_y >= 1024) { return y / 1024 + "K" } 1173 | else if (abs_y < 1 && y > 0) { return y.toFixed(2) } 1174 | else if (abs_y === 0) { return '' } 1175 | else { return y } 1176 | }; 1177 | Rickshaw.namespace("Rickshaw.Color.Palette"); 1178 | 1179 | Rickshaw.Color.Palette = function(args) { 1180 | 1181 | var color = new Rickshaw.Fixtures.Color(); 1182 | 1183 | args = args || {}; 1184 | this.schemes = {}; 1185 | 1186 | this.scheme = color.schemes[args.scheme] || args.scheme || color.schemes.colorwheel; 1187 | this.runningIndex = 0; 1188 | this.generatorIndex = 0; 1189 | 1190 | if (args.interpolatedStopCount) { 1191 | var schemeCount = this.scheme.length - 1; 1192 | var i, j, scheme = []; 1193 | for (i = 0; i < schemeCount; i++) { 1194 | scheme.push(this.scheme[i]); 1195 | var generator = d3.interpolateHsl(this.scheme[i], this.scheme[i + 1]); 1196 | for (j = 1; j < args.interpolatedStopCount; j++) { 1197 | scheme.push(generator((1 / args.interpolatedStopCount) * j)); 1198 | } 1199 | } 1200 | scheme.push(this.scheme[this.scheme.length - 1]); 1201 | this.scheme = scheme; 1202 | } 1203 | this.rotateCount = this.scheme.length; 1204 | 1205 | this.color = function(key) { 1206 | return this.scheme[key] || this.scheme[this.runningIndex++] || this.interpolateColor() || '#808080'; 1207 | }; 1208 | 1209 | this.interpolateColor = function() { 1210 | if (!Array.isArray(this.scheme)) return; 1211 | var color; 1212 | if (this.generatorIndex == this.rotateCount * 2 - 1) { 1213 | color = d3.interpolateHsl(this.scheme[this.generatorIndex], this.scheme[0])(0.5); 1214 | this.generatorIndex = 0; 1215 | this.rotateCount *= 2; 1216 | } else { 1217 | color = d3.interpolateHsl(this.scheme[this.generatorIndex], this.scheme[this.generatorIndex + 1])(0.5); 1218 | this.generatorIndex++; 1219 | } 1220 | this.scheme.push(color); 1221 | return color; 1222 | }; 1223 | 1224 | }; 1225 | Rickshaw.namespace('Rickshaw.Graph.Ajax'); 1226 | 1227 | Rickshaw.Graph.Ajax = Rickshaw.Class.create( { 1228 | 1229 | initialize: function(args) { 1230 | 1231 | this.dataURL = args.dataURL; 1232 | 1233 | this.onData = args.onData || function(d) { return d }; 1234 | this.onComplete = args.onComplete || function() {}; 1235 | this.onError = args.onError || function() {}; 1236 | 1237 | this.args = args; // pass through to Rickshaw.Graph 1238 | 1239 | this.request(); 1240 | }, 1241 | 1242 | request: function() { 1243 | 1244 | jQuery.ajax( { 1245 | url: this.dataURL, 1246 | dataType: 'json', 1247 | success: this.success.bind(this), 1248 | error: this.error.bind(this) 1249 | } ); 1250 | }, 1251 | 1252 | error: function() { 1253 | 1254 | console.log("error loading dataURL: " + this.dataURL); 1255 | this.onError(this); 1256 | }, 1257 | 1258 | success: function(data, status) { 1259 | 1260 | data = this.onData(data); 1261 | this.args.series = this._splice({ data: data, series: this.args.series }); 1262 | 1263 | this.graph = this.graph || new Rickshaw.Graph(this.args); 1264 | this.graph.render(); 1265 | 1266 | this.onComplete(this); 1267 | }, 1268 | 1269 | _splice: function(args) { 1270 | 1271 | var data = args.data; 1272 | var series = args.series; 1273 | 1274 | if (!args.series) return data; 1275 | 1276 | series.forEach( function(s) { 1277 | 1278 | var seriesKey = s.key || s.name; 1279 | if (!seriesKey) throw "series needs a key or a name"; 1280 | 1281 | data.forEach( function(d) { 1282 | 1283 | var dataKey = d.key || d.name; 1284 | if (!dataKey) throw "data needs a key or a name"; 1285 | 1286 | if (seriesKey == dataKey) { 1287 | var properties = ['color', 'name', 'data']; 1288 | properties.forEach( function(p) { 1289 | if (d[p]) s[p] = d[p]; 1290 | } ); 1291 | } 1292 | } ); 1293 | } ); 1294 | 1295 | return series; 1296 | } 1297 | } ); 1298 | 1299 | Rickshaw.namespace('Rickshaw.Graph.Annotate'); 1300 | 1301 | Rickshaw.Graph.Annotate = function(args) { 1302 | 1303 | var graph = this.graph = args.graph; 1304 | this.elements = { timeline: args.element }; 1305 | 1306 | var self = this; 1307 | 1308 | this.data = {}; 1309 | 1310 | this.elements.timeline.classList.add('rickshaw_annotation_timeline'); 1311 | 1312 | this.add = function(time, content, end_time) { 1313 | self.data[time] = self.data[time] || {'boxes': []}; 1314 | self.data[time].boxes.push({content: content, end: end_time}); 1315 | }; 1316 | 1317 | this.update = function() { 1318 | 1319 | Rickshaw.keys(self.data).forEach( function(time) { 1320 | 1321 | var annotation = self.data[time]; 1322 | var left = self.graph.x(time); 1323 | 1324 | if (left < 0 || left > self.graph.x.range()[1]) { 1325 | if (annotation.element) { 1326 | annotation.line.classList.add('offscreen'); 1327 | annotation.element.style.display = 'none'; 1328 | } 1329 | 1330 | annotation.boxes.forEach( function(box) { 1331 | if ( box.rangeElement ) box.rangeElement.classList.add('offscreen'); 1332 | }); 1333 | 1334 | return; 1335 | } 1336 | 1337 | if (!annotation.element) { 1338 | var element = annotation.element = document.createElement('div'); 1339 | element.classList.add('annotation'); 1340 | this.elements.timeline.appendChild(element); 1341 | element.addEventListener('click', function(e) { 1342 | element.classList.toggle('active'); 1343 | annotation.line.classList.toggle('active'); 1344 | annotation.boxes.forEach( function(box) { 1345 | if ( box.rangeElement ) box.rangeElement.classList.toggle('active'); 1346 | }); 1347 | }, false); 1348 | 1349 | } 1350 | 1351 | annotation.element.style.left = left + 'px'; 1352 | annotation.element.style.display = 'block'; 1353 | 1354 | annotation.boxes.forEach( function(box) { 1355 | 1356 | 1357 | var element = box.element; 1358 | 1359 | if (!element) { 1360 | element = box.element = document.createElement('div'); 1361 | element.classList.add('content'); 1362 | element.innerHTML = box.content; 1363 | annotation.element.appendChild(element); 1364 | 1365 | annotation.line = document.createElement('div'); 1366 | annotation.line.classList.add('annotation_line'); 1367 | self.graph.element.appendChild(annotation.line); 1368 | 1369 | if ( box.end ) { 1370 | box.rangeElement = document.createElement('div'); 1371 | box.rangeElement.classList.add('annotation_range'); 1372 | self.graph.element.appendChild(box.rangeElement); 1373 | } 1374 | 1375 | } 1376 | 1377 | if ( box.end ) { 1378 | 1379 | var annotationRangeStart = left; 1380 | var annotationRangeEnd = Math.min( self.graph.x(box.end), self.graph.x.range()[1] ); 1381 | 1382 | // annotation makes more sense at end 1383 | if ( annotationRangeStart > annotationRangeEnd ) { 1384 | annotationRangeEnd = left; 1385 | annotationRangeStart = Math.max( self.graph.x(box.end), self.graph.x.range()[0] ); 1386 | } 1387 | 1388 | var annotationRangeWidth = annotationRangeEnd - annotationRangeStart; 1389 | 1390 | box.rangeElement.style.left = annotationRangeStart + 'px'; 1391 | box.rangeElement.style.width = annotationRangeWidth + 'px'; 1392 | 1393 | box.rangeElement.classList.remove('offscreen'); 1394 | } 1395 | 1396 | annotation.line.classList.remove('offscreen'); 1397 | annotation.line.style.left = left + 'px'; 1398 | } ); 1399 | }, this ); 1400 | }; 1401 | 1402 | this.graph.onUpdate( function() { self.update() } ); 1403 | }; 1404 | Rickshaw.namespace('Rickshaw.Graph.Axis.Time'); 1405 | 1406 | Rickshaw.Graph.Axis.Time = function(args) { 1407 | 1408 | var self = this; 1409 | 1410 | this.graph = args.graph; 1411 | this.elements = []; 1412 | this.ticksTreatment = args.ticksTreatment || 'plain'; 1413 | this.fixedTimeUnit = args.timeUnit; 1414 | 1415 | var time = args.timeFixture || new Rickshaw.Fixtures.Time(); 1416 | 1417 | this.appropriateTimeUnit = function() { 1418 | 1419 | var unit; 1420 | var units = time.units; 1421 | 1422 | var domain = this.graph.x.domain(); 1423 | var rangeSeconds = domain[1] - domain[0]; 1424 | 1425 | units.forEach( function(u) { 1426 | if (Math.floor(rangeSeconds / u.seconds) >= 2) { 1427 | unit = unit || u; 1428 | } 1429 | } ); 1430 | 1431 | return (unit || time.units[time.units.length - 1]); 1432 | }; 1433 | 1434 | this.tickOffsets = function() { 1435 | 1436 | var domain = this.graph.x.domain(); 1437 | 1438 | var unit = this.fixedTimeUnit || this.appropriateTimeUnit(); 1439 | var count = Math.ceil((domain[1] - domain[0]) / unit.seconds); 1440 | 1441 | var runningTick = domain[0]; 1442 | 1443 | var offsets = []; 1444 | 1445 | for (var i = 0; i < count; i++) { 1446 | 1447 | var tickValue = time.ceil(runningTick, unit); 1448 | runningTick = tickValue + unit.seconds / 2; 1449 | 1450 | offsets.push( { value: tickValue, unit: unit } ); 1451 | } 1452 | 1453 | return offsets; 1454 | }; 1455 | 1456 | this.render = function() { 1457 | 1458 | this.elements.forEach( function(e) { 1459 | e.parentNode.removeChild(e); 1460 | } ); 1461 | 1462 | this.elements = []; 1463 | 1464 | var offsets = this.tickOffsets(); 1465 | 1466 | offsets.forEach( function(o) { 1467 | 1468 | if (self.graph.x(o.value) > self.graph.x.range()[1]) return; 1469 | 1470 | var element = document.createElement('div'); 1471 | element.style.left = self.graph.x(o.value) + 'px'; 1472 | element.classList.add('x_tick'); 1473 | element.classList.add(self.ticksTreatment); 1474 | 1475 | var title = document.createElement('div'); 1476 | title.classList.add('title'); 1477 | title.innerHTML = o.unit.formatter(new Date(o.value * 1000)); 1478 | element.appendChild(title); 1479 | 1480 | self.graph.element.appendChild(element); 1481 | self.elements.push(element); 1482 | 1483 | } ); 1484 | }; 1485 | 1486 | this.graph.onUpdate( function() { self.render() } ); 1487 | }; 1488 | 1489 | Rickshaw.namespace('Rickshaw.Graph.Axis.X'); 1490 | 1491 | Rickshaw.Graph.Axis.X = function(args) { 1492 | 1493 | var self = this; 1494 | var berthRate = 0.10; 1495 | 1496 | this.initialize = function(args) { 1497 | 1498 | this.graph = args.graph; 1499 | this.orientation = args.orientation || 'top'; 1500 | 1501 | this.pixelsPerTick = args.pixelsPerTick || 75; 1502 | if (args.ticks) this.staticTicks = args.ticks; 1503 | if (args.tickValues) this.tickValues = args.tickValues; 1504 | 1505 | this.tickSize = args.tickSize || 4; 1506 | this.ticksTreatment = args.ticksTreatment || 'plain'; 1507 | 1508 | if (args.element) { 1509 | 1510 | this.element = args.element; 1511 | this._discoverSize(args.element, args); 1512 | 1513 | this.vis = d3.select(args.element) 1514 | .append("svg:svg") 1515 | .attr('height', this.height) 1516 | .attr('width', this.width) 1517 | .attr('class', 'rickshaw_graph x_axis_d3'); 1518 | 1519 | this.element = this.vis[0][0]; 1520 | this.element.style.position = 'relative'; 1521 | 1522 | this.setSize({ width: args.width, height: args.height }); 1523 | 1524 | } else { 1525 | this.vis = this.graph.vis; 1526 | } 1527 | 1528 | this.graph.onUpdate( function() { self.render() } ); 1529 | }; 1530 | 1531 | this.setSize = function(args) { 1532 | 1533 | args = args || {}; 1534 | if (!this.element) return; 1535 | 1536 | this._discoverSize(this.element.parentNode, args); 1537 | 1538 | this.vis 1539 | .attr('height', this.height) 1540 | .attr('width', this.width * (1 + berthRate)); 1541 | 1542 | var berth = Math.floor(this.width * berthRate / 2); 1543 | this.element.style.left = -1 * berth + 'px'; 1544 | }; 1545 | 1546 | this.render = function() { 1547 | 1548 | if (this._renderWidth !== undefined && this.graph.width !== this._renderWidth) this.setSize({ auto: true }); 1549 | 1550 | var axis = d3.svg.axis().scale(this.graph.x).orient(this.orientation); 1551 | axis.tickFormat( args.tickFormat || function(x) { return x } ); 1552 | if (this.tickValues) axis.tickValues(this.tickValues); 1553 | 1554 | this.ticks = this.staticTicks || Math.floor(this.graph.width / this.pixelsPerTick); 1555 | 1556 | var berth = Math.floor(this.width * berthRate / 2) || 0; 1557 | var transform; 1558 | 1559 | if (this.orientation == 'top') { 1560 | var yOffset = this.height || this.graph.height; 1561 | transform = 'translate(' + berth + ',' + yOffset + ')'; 1562 | } else { 1563 | transform = 'translate(' + berth + ', 0)'; 1564 | } 1565 | 1566 | if (this.element) { 1567 | this.vis.selectAll('*').remove(); 1568 | } 1569 | 1570 | this.vis 1571 | .append("svg:g") 1572 | .attr("class", ["x_ticks_d3", this.ticksTreatment].join(" ")) 1573 | .attr("transform", transform) 1574 | .call(axis.ticks(this.ticks).tickSubdivide(0).tickSize(this.tickSize)); 1575 | 1576 | var gridSize = (this.orientation == 'bottom' ? 1 : -1) * this.graph.height; 1577 | 1578 | this.graph.vis 1579 | .append("svg:g") 1580 | .attr("class", "x_grid_d3") 1581 | .call(axis.ticks(this.ticks).tickSubdivide(0).tickSize(gridSize)) 1582 | .selectAll('text') 1583 | .each(function() { this.parentNode.setAttribute('data-x-value', this.textContent) }); 1584 | 1585 | this._renderHeight = this.graph.height; 1586 | }; 1587 | 1588 | this._discoverSize = function(element, args) { 1589 | 1590 | if (typeof window !== 'undefined') { 1591 | 1592 | var style = window.getComputedStyle(element, null); 1593 | var elementHeight = parseInt(style.getPropertyValue('height'), 10); 1594 | 1595 | if (!args.auto) { 1596 | var elementWidth = parseInt(style.getPropertyValue('width'), 10); 1597 | } 1598 | } 1599 | 1600 | this.width = (args.width || elementWidth || this.graph.width) * (1 + berthRate); 1601 | this.height = args.height || elementHeight || 40; 1602 | }; 1603 | 1604 | this.initialize(args); 1605 | }; 1606 | 1607 | Rickshaw.namespace('Rickshaw.Graph.Axis.Y'); 1608 | 1609 | Rickshaw.Graph.Axis.Y = Rickshaw.Class.create( { 1610 | 1611 | initialize: function(args) { 1612 | 1613 | this.graph = args.graph; 1614 | this.orientation = args.orientation || 'right'; 1615 | 1616 | this.pixelsPerTick = args.pixelsPerTick || 75; 1617 | if (args.ticks) this.staticTicks = args.ticks; 1618 | if (args.tickValues) this.tickValues = args.tickValues; 1619 | 1620 | this.tickSize = args.tickSize || 4; 1621 | this.ticksTreatment = args.ticksTreatment || 'plain'; 1622 | 1623 | this.tickFormat = args.tickFormat || function(y) { return y }; 1624 | 1625 | this.berthRate = 0.10; 1626 | 1627 | if (args.element) { 1628 | 1629 | this.element = args.element; 1630 | this.vis = d3.select(args.element) 1631 | .append("svg:svg") 1632 | .attr('class', 'rickshaw_graph y_axis'); 1633 | 1634 | this.element = this.vis[0][0]; 1635 | this.element.style.position = 'relative'; 1636 | 1637 | this.setSize({ width: args.width, height: args.height }); 1638 | 1639 | } else { 1640 | this.vis = this.graph.vis; 1641 | } 1642 | 1643 | var self = this; 1644 | this.graph.onUpdate( function() { self.render() } ); 1645 | }, 1646 | 1647 | setSize: function(args) { 1648 | 1649 | args = args || {}; 1650 | 1651 | if (!this.element) return; 1652 | 1653 | if (typeof window !== 'undefined') { 1654 | 1655 | var style = window.getComputedStyle(this.element.parentNode, null); 1656 | var elementWidth = parseInt(style.getPropertyValue('width'), 10); 1657 | 1658 | if (!args.auto) { 1659 | var elementHeight = parseInt(style.getPropertyValue('height'), 10); 1660 | } 1661 | } 1662 | 1663 | this.width = args.width || elementWidth || this.graph.width * this.berthRate; 1664 | this.height = args.height || elementHeight || this.graph.height; 1665 | 1666 | this.vis 1667 | .attr('width', this.width) 1668 | .attr('height', this.height * (1 + this.berthRate)); 1669 | 1670 | var berth = this.height * this.berthRate; 1671 | 1672 | if (this.orientation == 'left') { 1673 | this.element.style.top = -1 * berth + 'px'; 1674 | } 1675 | }, 1676 | 1677 | render: function() { 1678 | 1679 | if (this._renderHeight !== undefined && this.graph.height !== this._renderHeight) this.setSize({ auto: true }); 1680 | 1681 | this.ticks = this.staticTicks || Math.floor(this.graph.height / this.pixelsPerTick); 1682 | 1683 | var axis = this._drawAxis(this.graph.y); 1684 | 1685 | this._drawGrid(axis); 1686 | 1687 | this._renderHeight = this.graph.height; 1688 | }, 1689 | 1690 | _drawAxis: function(scale) { 1691 | var axis = d3.svg.axis().scale(scale).orient(this.orientation); 1692 | axis.tickFormat(this.tickFormat); 1693 | if (this.tickValues) axis.tickValues(this.tickValues); 1694 | 1695 | if (this.orientation == 'left') { 1696 | var berth = this.height * this.berthRate; 1697 | var transform = 'translate(' + this.width + ', ' + berth + ')'; 1698 | } 1699 | 1700 | if (this.element) { 1701 | this.vis.selectAll('*').remove(); 1702 | } 1703 | 1704 | this.vis 1705 | .append("svg:g") 1706 | .attr("class", ["y_ticks", this.ticksTreatment].join(" ")) 1707 | .attr("transform", transform) 1708 | .call(axis.ticks(this.ticks).tickSubdivide(0).tickSize(this.tickSize)); 1709 | 1710 | return axis; 1711 | }, 1712 | 1713 | _drawGrid: function(axis) { 1714 | var gridSize = (this.orientation == 'right' ? 1 : -1) * this.graph.width; 1715 | 1716 | this.graph.vis 1717 | .append("svg:g") 1718 | .attr("class", "y_grid") 1719 | .call(axis.ticks(this.ticks).tickSubdivide(0).tickSize(gridSize)) 1720 | .selectAll('text') 1721 | .each(function() { this.parentNode.setAttribute('data-y-value', this.textContent) }); 1722 | } 1723 | } ); 1724 | Rickshaw.namespace('Rickshaw.Graph.Axis.Y.Scaled'); 1725 | 1726 | Rickshaw.Graph.Axis.Y.Scaled = Rickshaw.Class.create( Rickshaw.Graph.Axis.Y, { 1727 | 1728 | initialize: function($super, args) { 1729 | 1730 | if (typeof(args.scale) === 'undefined') { 1731 | throw new Error('Scaled requires scale'); 1732 | } 1733 | 1734 | this.scale = args.scale; 1735 | 1736 | if (typeof(args.grid) === 'undefined') { 1737 | this.grid = true; 1738 | } else { 1739 | this.grid = args.grid; 1740 | } 1741 | 1742 | $super(args); 1743 | 1744 | }, 1745 | 1746 | _drawAxis: function($super, scale) { 1747 | // Adjust scale's domain to compensate for adjustments to the 1748 | // renderer's domain (e.g. padding). 1749 | var domain = this.scale.domain(); 1750 | var renderDomain = this.graph.renderer.domain().y; 1751 | 1752 | var extents = [ 1753 | Math.min.apply(Math, domain), 1754 | Math.max.apply(Math, domain)]; 1755 | 1756 | // A mapping from the ideal render domain [0, 1] to the extent 1757 | // of the original scale's domain. This is used to calculate 1758 | // the extents of the adjusted domain. 1759 | var extentMap = d3.scale.linear().domain([0, 1]).range(extents); 1760 | 1761 | var adjExtents = [ 1762 | extentMap(renderDomain[0]), 1763 | extentMap(renderDomain[1])]; 1764 | 1765 | // A mapping from the original domain to the adjusted domain. 1766 | var adjustment = d3.scale.linear().domain(extents).range(adjExtents); 1767 | 1768 | // Make a copy of the custom scale, apply the adjusted domain, and 1769 | // copy the range to match the graph's scale. 1770 | var adjustedScale = this.scale.copy() 1771 | .domain(domain.map(adjustment)) 1772 | .range(scale.range()); 1773 | 1774 | return $super(adjustedScale); 1775 | }, 1776 | 1777 | _drawGrid: function($super, axis) { 1778 | if (this.grid) { 1779 | // only draw the axis if the grid option is true 1780 | $super(axis); 1781 | } 1782 | } 1783 | } ); 1784 | Rickshaw.namespace('Rickshaw.Graph.Behavior.Series.Highlight'); 1785 | 1786 | Rickshaw.Graph.Behavior.Series.Highlight = function(args) { 1787 | 1788 | this.graph = args.graph; 1789 | this.legend = args.legend; 1790 | 1791 | var self = this; 1792 | 1793 | var colorSafe = {}; 1794 | var activeLine = null; 1795 | 1796 | var disabledColor = args.disabledColor || function(seriesColor) { 1797 | return d3.interpolateRgb(seriesColor, d3.rgb('#d8d8d8'))(0.8).toString(); 1798 | }; 1799 | 1800 | this.addHighlightEvents = function (l) { 1801 | 1802 | l.element.addEventListener( 'mouseover', function(e) { 1803 | 1804 | if (activeLine) return; 1805 | else activeLine = l; 1806 | 1807 | self.legend.lines.forEach( function(line) { 1808 | 1809 | if (l === line) { 1810 | 1811 | // if we're not in a stacked renderer bring active line to the top 1812 | if (self.graph.renderer.unstack && (line.series.renderer ? line.series.renderer.unstack : true)) { 1813 | 1814 | var seriesIndex = self.graph.series.indexOf(line.series); 1815 | line.originalIndex = seriesIndex; 1816 | 1817 | var series = self.graph.series.splice(seriesIndex, 1)[0]; 1818 | self.graph.series.push(series); 1819 | } 1820 | return; 1821 | } 1822 | 1823 | colorSafe[line.series.name] = colorSafe[line.series.name] || line.series.color; 1824 | line.series.color = disabledColor(line.series.color); 1825 | 1826 | } ); 1827 | 1828 | self.graph.update(); 1829 | 1830 | }, false ); 1831 | 1832 | l.element.addEventListener( 'mouseout', function(e) { 1833 | 1834 | if (!activeLine) return; 1835 | else activeLine = null; 1836 | 1837 | self.legend.lines.forEach( function(line) { 1838 | 1839 | // return reordered series to its original place 1840 | if (l === line && line.hasOwnProperty('originalIndex')) { 1841 | 1842 | var series = self.graph.series.pop(); 1843 | self.graph.series.splice(line.originalIndex, 0, series); 1844 | delete line.originalIndex; 1845 | } 1846 | 1847 | if (colorSafe[line.series.name]) { 1848 | line.series.color = colorSafe[line.series.name]; 1849 | } 1850 | } ); 1851 | 1852 | self.graph.update(); 1853 | 1854 | }, false ); 1855 | }; 1856 | 1857 | if (this.legend) { 1858 | this.legend.lines.forEach( function(l) { 1859 | self.addHighlightEvents(l); 1860 | } ); 1861 | } 1862 | 1863 | }; 1864 | Rickshaw.namespace('Rickshaw.Graph.Behavior.Series.Order'); 1865 | 1866 | Rickshaw.Graph.Behavior.Series.Order = function(args) { 1867 | 1868 | this.graph = args.graph; 1869 | this.legend = args.legend; 1870 | 1871 | var self = this; 1872 | 1873 | if (typeof window.jQuery == 'undefined') { 1874 | throw "couldn't find jQuery at window.jQuery"; 1875 | } 1876 | 1877 | if (typeof window.jQuery.ui == 'undefined') { 1878 | throw "couldn't find jQuery UI at window.jQuery.ui"; 1879 | } 1880 | 1881 | jQuery(function() { 1882 | jQuery(self.legend.list).sortable( { 1883 | containment: 'parent', 1884 | tolerance: 'pointer', 1885 | update: function( event, ui ) { 1886 | var series = []; 1887 | jQuery(self.legend.list).find('li').each( function(index, item) { 1888 | if (!item.series) return; 1889 | series.push(item.series); 1890 | } ); 1891 | 1892 | for (var i = self.graph.series.length - 1; i >= 0; i--) { 1893 | self.graph.series[i] = series.shift(); 1894 | } 1895 | 1896 | self.graph.update(); 1897 | } 1898 | } ); 1899 | jQuery(self.legend.list).disableSelection(); 1900 | }); 1901 | 1902 | //hack to make jquery-ui sortable behave 1903 | this.graph.onUpdate( function() { 1904 | var h = window.getComputedStyle(self.legend.element).height; 1905 | self.legend.element.style.height = h; 1906 | } ); 1907 | }; 1908 | Rickshaw.namespace('Rickshaw.Graph.Behavior.Series.Toggle'); 1909 | 1910 | Rickshaw.Graph.Behavior.Series.Toggle = function(args) { 1911 | 1912 | this.graph = args.graph; 1913 | this.legend = args.legend; 1914 | 1915 | var self = this; 1916 | 1917 | this.addAnchor = function(line) { 1918 | 1919 | var anchor = document.createElement('a'); 1920 | anchor.innerHTML = '✔'; 1921 | anchor.classList.add('action'); 1922 | line.element.insertBefore(anchor, line.element.firstChild); 1923 | 1924 | anchor.onclick = function(e) { 1925 | if (line.series.disabled) { 1926 | line.series.enable(); 1927 | line.element.classList.remove('disabled'); 1928 | } else { 1929 | if (this.graph.series.filter(function(s) { return !s.disabled }).length <= 1) return; 1930 | line.series.disable(); 1931 | line.element.classList.add('disabled'); 1932 | } 1933 | 1934 | self.graph.update(); 1935 | 1936 | }.bind(this); 1937 | 1938 | var label = line.element.getElementsByTagName('span')[0]; 1939 | label.onclick = function(e){ 1940 | 1941 | var disableAllOtherLines = line.series.disabled; 1942 | if ( ! disableAllOtherLines ) { 1943 | for ( var i = 0; i < self.legend.lines.length; i++ ) { 1944 | var l = self.legend.lines[i]; 1945 | if ( line.series === l.series ) { 1946 | // noop 1947 | } else if ( l.series.disabled ) { 1948 | // noop 1949 | } else { 1950 | disableAllOtherLines = true; 1951 | break; 1952 | } 1953 | } 1954 | } 1955 | 1956 | // show all or none 1957 | if ( disableAllOtherLines ) { 1958 | 1959 | // these must happen first or else we try ( and probably fail ) to make a no line graph 1960 | line.series.enable(); 1961 | line.element.classList.remove('disabled'); 1962 | 1963 | self.legend.lines.forEach(function(l){ 1964 | if ( line.series === l.series ) { 1965 | // noop 1966 | } else { 1967 | l.series.disable(); 1968 | l.element.classList.add('disabled'); 1969 | } 1970 | }); 1971 | 1972 | } else { 1973 | 1974 | self.legend.lines.forEach(function(l){ 1975 | l.series.enable(); 1976 | l.element.classList.remove('disabled'); 1977 | }); 1978 | 1979 | } 1980 | 1981 | self.graph.update(); 1982 | 1983 | }; 1984 | 1985 | }; 1986 | 1987 | if (this.legend) { 1988 | 1989 | var $ = jQuery; 1990 | if (typeof $ != 'undefined' && $(this.legend.list).sortable) { 1991 | 1992 | $(this.legend.list).sortable( { 1993 | start: function(event, ui) { 1994 | ui.item.bind('no.onclick', 1995 | function(event) { 1996 | event.preventDefault(); 1997 | } 1998 | ); 1999 | }, 2000 | stop: function(event, ui) { 2001 | setTimeout(function(){ 2002 | ui.item.unbind('no.onclick'); 2003 | }, 250); 2004 | } 2005 | }); 2006 | } 2007 | 2008 | this.legend.lines.forEach( function(l) { 2009 | self.addAnchor(l); 2010 | } ); 2011 | } 2012 | 2013 | this._addBehavior = function() { 2014 | 2015 | this.graph.series.forEach( function(s) { 2016 | 2017 | s.disable = function() { 2018 | 2019 | if (self.graph.series.length <= 1) { 2020 | throw('only one series left'); 2021 | } 2022 | 2023 | s.disabled = true; 2024 | }; 2025 | 2026 | s.enable = function() { 2027 | s.disabled = false; 2028 | }; 2029 | } ); 2030 | }; 2031 | this._addBehavior(); 2032 | 2033 | this.updateBehaviour = function () { this._addBehavior() }; 2034 | 2035 | }; 2036 | Rickshaw.namespace('Rickshaw.Graph.HoverDetail'); 2037 | 2038 | Rickshaw.Graph.HoverDetail = Rickshaw.Class.create({ 2039 | 2040 | initialize: function(args) { 2041 | 2042 | var graph = this.graph = args.graph; 2043 | 2044 | this.xFormatter = args.xFormatter || function(x) { 2045 | return new Date( x * 1000 ).toUTCString(); 2046 | }; 2047 | 2048 | this.yFormatter = args.yFormatter || function(y) { 2049 | return y === null ? y : y.toFixed(2); 2050 | }; 2051 | 2052 | var element = this.element = document.createElement('div'); 2053 | element.className = 'detail'; 2054 | 2055 | this.visible = true; 2056 | graph.element.appendChild(element); 2057 | 2058 | this.lastEvent = null; 2059 | this._addListeners(); 2060 | 2061 | this.onShow = args.onShow; 2062 | this.onHide = args.onHide; 2063 | this.onRender = args.onRender; 2064 | 2065 | this.formatter = args.formatter || this.formatter; 2066 | 2067 | }, 2068 | 2069 | formatter: function(series, x, y, formattedX, formattedY, d) { 2070 | return series.name + ': ' + formattedY; 2071 | }, 2072 | 2073 | update: function(e) { 2074 | 2075 | e = e || this.lastEvent; 2076 | if (!e) return; 2077 | this.lastEvent = e; 2078 | 2079 | if (!e.target.nodeName.match(/^(path|svg|rect|circle)$/)) return; 2080 | 2081 | var graph = this.graph; 2082 | 2083 | var eventX = e.offsetX || e.layerX; 2084 | var eventY = e.offsetY || e.layerY; 2085 | 2086 | var j = 0; 2087 | var points = []; 2088 | var nearestPoint; 2089 | 2090 | this.graph.series.active().forEach( function(series) { 2091 | 2092 | var data = this.graph.stackedData[j++]; 2093 | 2094 | if (!data.length) 2095 | return; 2096 | 2097 | var domainX = graph.x.invert(eventX); 2098 | 2099 | var domainIndexScale = d3.scale.linear() 2100 | .domain([data[0].x, data.slice(-1)[0].x]) 2101 | .range([0, data.length - 1]); 2102 | 2103 | var approximateIndex = Math.round(domainIndexScale(domainX)); 2104 | if (approximateIndex == data.length - 1) approximateIndex--; 2105 | 2106 | var dataIndex = Math.min(approximateIndex || 0, data.length - 1); 2107 | 2108 | for (var i = approximateIndex; i < data.length - 1;) { 2109 | 2110 | if (!data[i] || !data[i + 1]) break; 2111 | 2112 | if (data[i].x <= domainX && data[i + 1].x > domainX) { 2113 | dataIndex = Math.abs(domainX - data[i].x) < Math.abs(domainX - data[i + 1].x) ? i : i + 1; 2114 | break; 2115 | } 2116 | 2117 | if (data[i + 1].x <= domainX) { i++ } else { i-- } 2118 | } 2119 | 2120 | if (dataIndex < 0) dataIndex = 0; 2121 | var value = data[dataIndex]; 2122 | 2123 | var distance = Math.sqrt( 2124 | Math.pow(Math.abs(graph.x(value.x) - eventX), 2) + 2125 | Math.pow(Math.abs(graph.y(value.y + value.y0) - eventY), 2) 2126 | ); 2127 | 2128 | var xFormatter = series.xFormatter || this.xFormatter; 2129 | var yFormatter = series.yFormatter || this.yFormatter; 2130 | 2131 | var point = { 2132 | formattedXValue: xFormatter(value.x), 2133 | formattedYValue: yFormatter(series.scale ? series.scale.invert(value.y) : value.y), 2134 | series: series, 2135 | value: value, 2136 | distance: distance, 2137 | order: j, 2138 | name: series.name 2139 | }; 2140 | 2141 | if (!nearestPoint || distance < nearestPoint.distance) { 2142 | nearestPoint = point; 2143 | } 2144 | 2145 | points.push(point); 2146 | 2147 | }, this ); 2148 | 2149 | if (!nearestPoint) 2150 | return; 2151 | 2152 | nearestPoint.active = true; 2153 | 2154 | var domainX = nearestPoint.value.x; 2155 | var formattedXValue = nearestPoint.formattedXValue; 2156 | 2157 | this.element.innerHTML = ''; 2158 | this.element.style.left = graph.x(domainX) + 'px'; 2159 | 2160 | this.visible && this.render( { 2161 | points: points, 2162 | detail: points, // for backwards compatibility 2163 | mouseX: eventX, 2164 | mouseY: eventY, 2165 | formattedXValue: formattedXValue, 2166 | domainX: domainX 2167 | } ); 2168 | }, 2169 | 2170 | hide: function() { 2171 | this.visible = false; 2172 | this.element.classList.add('inactive'); 2173 | 2174 | if (typeof this.onHide == 'function') { 2175 | this.onHide(); 2176 | } 2177 | }, 2178 | 2179 | show: function() { 2180 | this.visible = true; 2181 | this.element.classList.remove('inactive'); 2182 | 2183 | if (typeof this.onShow == 'function') { 2184 | this.onShow(); 2185 | } 2186 | }, 2187 | 2188 | render: function(args) { 2189 | 2190 | var graph = this.graph; 2191 | var points = args.points; 2192 | var point = points.filter( function(p) { return p.active } ).shift(); 2193 | 2194 | if (point.value.y === null) return; 2195 | 2196 | var formattedXValue = point.formattedXValue; 2197 | var formattedYValue = point.formattedYValue; 2198 | 2199 | this.element.innerHTML = ''; 2200 | this.element.style.left = graph.x(point.value.x) + 'px'; 2201 | 2202 | var xLabel = document.createElement('div'); 2203 | 2204 | xLabel.className = 'x_label'; 2205 | xLabel.innerHTML = formattedXValue; 2206 | this.element.appendChild(xLabel); 2207 | 2208 | var item = document.createElement('div'); 2209 | 2210 | item.className = 'item'; 2211 | 2212 | // invert the scale if this series displays using a scale 2213 | var series = point.series; 2214 | var actualY = series.scale ? series.scale.invert(point.value.y) : point.value.y; 2215 | 2216 | item.innerHTML = this.formatter(series, point.value.x, actualY, formattedXValue, formattedYValue, point); 2217 | item.style.top = this.graph.y(point.value.y0 + point.value.y) + 'px'; 2218 | 2219 | this.element.appendChild(item); 2220 | 2221 | var dot = document.createElement('div'); 2222 | 2223 | dot.className = 'dot'; 2224 | dot.style.top = item.style.top; 2225 | dot.style.borderColor = series.color; 2226 | 2227 | this.element.appendChild(dot); 2228 | 2229 | if (point.active) { 2230 | item.classList.add('active'); 2231 | dot.classList.add('active'); 2232 | } 2233 | 2234 | // Assume left alignment until the element has been displayed and 2235 | // bounding box calculations are possible. 2236 | var alignables = [xLabel, item]; 2237 | alignables.forEach(function(el) { 2238 | el.classList.add('left'); 2239 | }); 2240 | 2241 | this.show(); 2242 | 2243 | // If left-alignment results in any error, try right-alignment. 2244 | var leftAlignError = this._calcLayoutError(alignables); 2245 | if (leftAlignError > 0) { 2246 | alignables.forEach(function(el) { 2247 | el.classList.remove('left'); 2248 | el.classList.add('right'); 2249 | }); 2250 | 2251 | // If right-alignment is worse than left alignment, switch back. 2252 | var rightAlignError = this._calcLayoutError(alignables); 2253 | if (rightAlignError > leftAlignError) { 2254 | alignables.forEach(function(el) { 2255 | el.classList.remove('right'); 2256 | el.classList.add('left'); 2257 | }); 2258 | } 2259 | } 2260 | 2261 | if (typeof this.onRender == 'function') { 2262 | this.onRender(args); 2263 | } 2264 | }, 2265 | 2266 | _calcLayoutError: function(alignables) { 2267 | // Layout error is calculated as the number of linear pixels by which 2268 | // an alignable extends past the left or right edge of the parent. 2269 | var parentRect = this.element.parentNode.getBoundingClientRect(); 2270 | 2271 | var error = 0; 2272 | var alignRight = alignables.forEach(function(el) { 2273 | var rect = el.getBoundingClientRect(); 2274 | if (!rect.width) { 2275 | return; 2276 | } 2277 | 2278 | if (rect.right > parentRect.right) { 2279 | error += rect.right - parentRect.right; 2280 | } 2281 | 2282 | if (rect.left < parentRect.left) { 2283 | error += parentRect.left - rect.left; 2284 | } 2285 | }); 2286 | return error; 2287 | }, 2288 | 2289 | _addListeners: function() { 2290 | 2291 | this.graph.element.addEventListener( 2292 | 'mousemove', 2293 | function(e) { 2294 | this.visible = true; 2295 | this.update(e); 2296 | }.bind(this), 2297 | false 2298 | ); 2299 | 2300 | this.graph.onUpdate( function() { this.update() }.bind(this) ); 2301 | 2302 | this.graph.element.addEventListener( 2303 | 'mouseout', 2304 | function(e) { 2305 | if (e.relatedTarget && !(e.relatedTarget.compareDocumentPosition(this.graph.element) & Node.DOCUMENT_POSITION_CONTAINS)) { 2306 | this.hide(); 2307 | } 2308 | }.bind(this), 2309 | false 2310 | ); 2311 | } 2312 | }); 2313 | Rickshaw.namespace('Rickshaw.Graph.JSONP'); 2314 | 2315 | Rickshaw.Graph.JSONP = Rickshaw.Class.create( Rickshaw.Graph.Ajax, { 2316 | 2317 | request: function() { 2318 | 2319 | jQuery.ajax( { 2320 | url: this.dataURL, 2321 | dataType: 'jsonp', 2322 | success: this.success.bind(this), 2323 | error: this.error.bind(this) 2324 | } ); 2325 | } 2326 | } ); 2327 | Rickshaw.namespace('Rickshaw.Graph.Legend'); 2328 | 2329 | Rickshaw.Graph.Legend = Rickshaw.Class.create( { 2330 | 2331 | className: 'rickshaw_legend', 2332 | 2333 | initialize: function(args) { 2334 | this.element = args.element; 2335 | this.graph = args.graph; 2336 | this.naturalOrder = args.naturalOrder; 2337 | 2338 | this.element.classList.add(this.className); 2339 | 2340 | this.list = document.createElement('ul'); 2341 | this.element.appendChild(this.list); 2342 | 2343 | this.render(); 2344 | 2345 | // we could bind this.render.bind(this) here 2346 | // but triggering the re-render would lose the added 2347 | // behavior of the series toggle 2348 | this.graph.onUpdate( function() {} ); 2349 | }, 2350 | 2351 | render: function() { 2352 | var self = this; 2353 | 2354 | while ( this.list.firstChild ) { 2355 | this.list.removeChild( this.list.firstChild ); 2356 | } 2357 | this.lines = []; 2358 | 2359 | var series = this.graph.series 2360 | .map( function(s) { return s } ); 2361 | 2362 | if (!this.naturalOrder) { 2363 | series = series.reverse(); 2364 | } 2365 | 2366 | series.forEach( function(s) { 2367 | self.addLine(s); 2368 | } ); 2369 | 2370 | 2371 | }, 2372 | 2373 | addLine: function (series) { 2374 | var line = document.createElement('li'); 2375 | line.className = 'line'; 2376 | if (series.disabled) { 2377 | line.className += ' disabled'; 2378 | } 2379 | if (series.className) { 2380 | d3.select(line).classed(series.className, true); 2381 | } 2382 | var swatch = document.createElement('div'); 2383 | swatch.className = 'swatch'; 2384 | swatch.style.backgroundColor = series.color; 2385 | 2386 | line.appendChild(swatch); 2387 | 2388 | var label = document.createElement('span'); 2389 | label.className = 'label'; 2390 | label.innerHTML = series.name; 2391 | 2392 | line.appendChild(label); 2393 | this.list.appendChild(line); 2394 | 2395 | line.series = series; 2396 | 2397 | if (series.noLegend) { 2398 | line.style.display = 'none'; 2399 | } 2400 | 2401 | var _line = { element: line, series: series }; 2402 | if (this.shelving) { 2403 | this.shelving.addAnchor(_line); 2404 | this.shelving.updateBehaviour(); 2405 | } 2406 | if (this.highlighter) { 2407 | this.highlighter.addHighlightEvents(_line); 2408 | } 2409 | this.lines.push(_line); 2410 | return line; 2411 | } 2412 | } ); 2413 | 2414 | Rickshaw.namespace('Rickshaw.Graph.RangeSlider'); 2415 | 2416 | Rickshaw.Graph.RangeSlider = Rickshaw.Class.create({ 2417 | 2418 | initialize: function(args) { 2419 | 2420 | var element = this.element = args.element; 2421 | var graph = this.graph = args.graph; 2422 | 2423 | this.slideCallbacks = []; 2424 | 2425 | this.build(); 2426 | 2427 | graph.onUpdate( function() { this.update() }.bind(this) ); 2428 | }, 2429 | 2430 | build: function() { 2431 | 2432 | var element = this.element; 2433 | var graph = this.graph; 2434 | var $ = jQuery; 2435 | 2436 | var domain = graph.dataDomain(); 2437 | var self = this; 2438 | 2439 | $( function() { 2440 | $(element).slider( { 2441 | range: true, 2442 | min: domain[0], 2443 | max: domain[1], 2444 | values: [ 2445 | domain[0], 2446 | domain[1] 2447 | ], 2448 | slide: function( event, ui ) { 2449 | 2450 | if (ui.values[1] <= ui.values[0]) return; 2451 | 2452 | graph.window.xMin = ui.values[0]; 2453 | graph.window.xMax = ui.values[1]; 2454 | graph.update(); 2455 | 2456 | var domain = graph.dataDomain(); 2457 | 2458 | // if we're at an extreme, stick there 2459 | if (domain[0] == ui.values[0]) { 2460 | graph.window.xMin = undefined; 2461 | } 2462 | 2463 | if (domain[1] == ui.values[1]) { 2464 | graph.window.xMax = undefined; 2465 | } 2466 | 2467 | self.slideCallbacks.forEach(function(callback) { 2468 | callback(graph, graph.window.xMin, graph.window.xMax); 2469 | }); 2470 | } 2471 | } ); 2472 | } ); 2473 | 2474 | $(element)[0].style.width = graph.width + 'px'; 2475 | 2476 | }, 2477 | 2478 | update: function() { 2479 | 2480 | var element = this.element; 2481 | var graph = this.graph; 2482 | var $ = jQuery; 2483 | 2484 | var values = $(element).slider('option', 'values'); 2485 | 2486 | var domain = graph.dataDomain(); 2487 | 2488 | $(element).slider('option', 'min', domain[0]); 2489 | $(element).slider('option', 'max', domain[1]); 2490 | 2491 | if (graph.window.xMin == null) { 2492 | values[0] = domain[0]; 2493 | } 2494 | if (graph.window.xMax == null) { 2495 | values[1] = domain[1]; 2496 | } 2497 | 2498 | $(element).slider('option', 'values', values); 2499 | }, 2500 | 2501 | onSlide: function(callback) { 2502 | this.slideCallbacks.push(callback); 2503 | } 2504 | }); 2505 | 2506 | Rickshaw.namespace('Rickshaw.Graph.RangeSlider.Preview'); 2507 | 2508 | Rickshaw.Graph.RangeSlider.Preview = Rickshaw.Class.create({ 2509 | 2510 | initialize: function(args) { 2511 | 2512 | if (!args.element) throw "Rickshaw.Graph.RangeSlider.Preview needs a reference to an element"; 2513 | if (!args.graph && !args.graphs) throw "Rickshaw.Graph.RangeSlider.Preview needs a reference to an graph or an array of graphs"; 2514 | 2515 | this.element = args.element; 2516 | this.element.style.position = 'relative'; 2517 | 2518 | this.graphs = args.graph ? [ args.graph ] : args.graphs; 2519 | 2520 | this.defaults = { 2521 | height: 75, 2522 | width: 400, 2523 | gripperColor: undefined, 2524 | frameTopThickness: 3, 2525 | frameHandleThickness: 10, 2526 | frameColor: "#d4d4d4", 2527 | frameOpacity: 1, 2528 | minimumFrameWidth: 0, 2529 | heightRatio: 0.2 2530 | }; 2531 | 2532 | this.heightRatio = args.heightRatio || this.defaults.heightRatio; 2533 | this.defaults.gripperColor = d3.rgb(this.defaults.frameColor).darker().toString(); 2534 | 2535 | this.configureCallbacks = []; 2536 | this.slideCallbacks = []; 2537 | 2538 | this.previews = []; 2539 | 2540 | if (!args.width) this.widthFromGraph = true; 2541 | if (!args.height) this.heightFromGraph = true; 2542 | 2543 | if (this.widthFromGraph || this.heightFromGraph) { 2544 | this.graphs[0].onConfigure(function () { 2545 | this.configure(args); this.render(); 2546 | }.bind(this)); 2547 | } 2548 | 2549 | args.width = args.width || this.graphs[0].width || this.defaults.width; 2550 | args.height = args.height || this.graphs[0].height * this.heightRatio || this.defaults.height; 2551 | 2552 | this.configure(args); 2553 | this.render(); 2554 | }, 2555 | 2556 | onSlide: function(callback) { 2557 | this.slideCallbacks.push(callback); 2558 | }, 2559 | 2560 | onConfigure: function(callback) { 2561 | this.configureCallbacks.push(callback); 2562 | }, 2563 | 2564 | configure: function(args) { 2565 | 2566 | this.config = this.config || {}; 2567 | 2568 | this.configureCallbacks.forEach(function(callback) { 2569 | callback(args); 2570 | }); 2571 | 2572 | Rickshaw.keys(this.defaults).forEach(function(k) { 2573 | this.config[k] = k in args ? args[k] 2574 | : k in this.config ? this.config[k] 2575 | : this.defaults[k]; 2576 | }, this); 2577 | 2578 | if ('width' in args || 'height' in args) { 2579 | 2580 | if (this.widthFromGraph) { 2581 | this.config.width = this.graphs[0].width; 2582 | } 2583 | 2584 | if (this.heightFromGraph) { 2585 | this.config.height = this.graphs[0].height * this.heightRatio; 2586 | this.previewHeight = this.config.height; 2587 | } 2588 | 2589 | this.previews.forEach(function(preview) { 2590 | 2591 | var height = this.previewHeight / this.graphs.length - this.config.frameTopThickness * 2; 2592 | var width = this.config.width - this.config.frameHandleThickness * 2; 2593 | preview.setSize({ width: width, height: height }); 2594 | 2595 | if (this.svg) { 2596 | var svgHeight = height + this.config.frameHandleThickness * 2; 2597 | var svgWidth = width + this.config.frameHandleThickness * 2; 2598 | this.svg.style("width", svgWidth + "px"); 2599 | this.svg.style("height", svgHeight + "px"); 2600 | } 2601 | }, this); 2602 | } 2603 | }, 2604 | 2605 | render: function() { 2606 | 2607 | var self = this; 2608 | 2609 | this.svg = d3.select(this.element) 2610 | .selectAll("svg.rickshaw_range_slider_preview") 2611 | .data([null]); 2612 | 2613 | this.previewHeight = this.config.height - (this.config.frameTopThickness * 2); 2614 | this.previewWidth = this.config.width - (this.config.frameHandleThickness * 2); 2615 | 2616 | this.currentFrame = [0, this.previewWidth]; 2617 | 2618 | var buildGraph = function(parent, index) { 2619 | 2620 | var graphArgs = Rickshaw.extend({}, parent.config); 2621 | var height = self.previewHeight / self.graphs.length; 2622 | var renderer = parent.renderer.name; 2623 | 2624 | Rickshaw.extend(graphArgs, { 2625 | element: this.appendChild(document.createElement("div")), 2626 | height: height, 2627 | width: self.previewWidth, 2628 | series: parent.series, 2629 | renderer: renderer 2630 | }); 2631 | 2632 | var graph = new Rickshaw.Graph(graphArgs); 2633 | self.previews.push(graph); 2634 | 2635 | parent.onUpdate(function() { graph.render(); self.render() }); 2636 | 2637 | parent.onConfigure(function(args) { 2638 | // don't propagate height 2639 | delete args.height; 2640 | args.width = args.width - self.config.frameHandleThickness * 2; 2641 | graph.configure(args); 2642 | graph.render(); 2643 | }); 2644 | 2645 | graph.render(); 2646 | }; 2647 | 2648 | var graphContainer = d3.select(this.element) 2649 | .selectAll("div.rickshaw_range_slider_preview_container") 2650 | .data(this.graphs); 2651 | 2652 | var translateCommand = "translate(" + 2653 | this.config.frameHandleThickness + "px, " + 2654 | this.config.frameTopThickness + "px)"; 2655 | 2656 | graphContainer.enter() 2657 | .append("div") 2658 | .classed("rickshaw_range_slider_preview_container", true) 2659 | .style("-webkit-transform", translateCommand) 2660 | .style("-moz-transform", translateCommand) 2661 | .style("-ms-transform", translateCommand) 2662 | .style("transform", translateCommand) 2663 | .each(buildGraph); 2664 | 2665 | graphContainer.exit() 2666 | .remove(); 2667 | 2668 | // Use the first graph as the "master" for the frame state 2669 | var masterGraph = this.graphs[0]; 2670 | 2671 | var domainScale = d3.scale.linear() 2672 | .domain([0, this.previewWidth]) 2673 | .range(masterGraph.dataDomain()); 2674 | 2675 | var currentWindow = [masterGraph.window.xMin, masterGraph.window.xMax]; 2676 | 2677 | this.currentFrame[0] = currentWindow[0] === undefined ? 2678 | 0 : Math.round(domainScale.invert(currentWindow[0])); 2679 | 2680 | if (this.currentFrame[0] < 0) this.currentFrame[0] = 0; 2681 | 2682 | this.currentFrame[1] = currentWindow[1] === undefined ? 2683 | this.previewWidth : domainScale.invert(currentWindow[1]); 2684 | 2685 | if (this.currentFrame[1] - this.currentFrame[0] < self.config.minimumFrameWidth) { 2686 | this.currentFrame[1] = (this.currentFrame[0] || 0) + self.config.minimumFrameWidth; 2687 | } 2688 | 2689 | this.svg.enter() 2690 | .append("svg") 2691 | .classed("rickshaw_range_slider_preview", true) 2692 | .style("height", this.config.height + "px") 2693 | .style("width", this.config.width + "px") 2694 | .style("position", "absolute") 2695 | .style("top", 0); 2696 | 2697 | this._renderDimming(); 2698 | this._renderFrame(); 2699 | this._renderGrippers(); 2700 | this._renderHandles(); 2701 | this._renderMiddle(); 2702 | 2703 | this._registerMouseEvents(); 2704 | }, 2705 | 2706 | _renderDimming: function() { 2707 | 2708 | var element = this.svg 2709 | .selectAll("path.dimming") 2710 | .data([null]); 2711 | 2712 | element.enter() 2713 | .append("path") 2714 | .attr("fill", "white") 2715 | .attr("fill-opacity", "0.7") 2716 | .attr("fill-rule", "evenodd") 2717 | .classed("dimming", true); 2718 | 2719 | var path = ""; 2720 | path += " M " + this.config.frameHandleThickness + " " + this.config.frameTopThickness; 2721 | path += " h " + this.previewWidth; 2722 | path += " v " + this.previewHeight; 2723 | path += " h " + -this.previewWidth; 2724 | path += " z "; 2725 | path += " M " + Math.max(this.currentFrame[0], this.config.frameHandleThickness) + " " + this.config.frameTopThickness; 2726 | path += " H " + Math.min(this.currentFrame[1] + this.config.frameHandleThickness * 2, this.previewWidth + this.config.frameHandleThickness); 2727 | path += " v " + this.previewHeight; 2728 | path += " H " + Math.max(this.currentFrame[0], this.config.frameHandleThickness); 2729 | path += " z"; 2730 | 2731 | element.attr("d", path); 2732 | }, 2733 | 2734 | _renderFrame: function() { 2735 | 2736 | var element = this.svg 2737 | .selectAll("path.frame") 2738 | .data([null]); 2739 | 2740 | element.enter() 2741 | .append("path") 2742 | .attr("stroke", "white") 2743 | .attr("stroke-width", "1px") 2744 | .attr("stroke-linejoin", "round") 2745 | .attr("fill", this.config.frameColor) 2746 | .attr("fill-opacity", this.config.frameOpacity) 2747 | .attr("fill-rule", "evenodd") 2748 | .classed("frame", true); 2749 | 2750 | var path = ""; 2751 | path += " M " + this.currentFrame[0] + " 0"; 2752 | path += " H " + (this.currentFrame[1] + (this.config.frameHandleThickness * 2)); 2753 | path += " V " + this.config.height; 2754 | path += " H " + (this.currentFrame[0]); 2755 | path += " z"; 2756 | path += " M " + (this.currentFrame[0] + this.config.frameHandleThickness) + " " + this.config.frameTopThickness; 2757 | path += " H " + (this.currentFrame[1] + this.config.frameHandleThickness); 2758 | path += " v " + this.previewHeight; 2759 | path += " H " + (this.currentFrame[0] + this.config.frameHandleThickness); 2760 | path += " z"; 2761 | 2762 | element.attr("d", path); 2763 | }, 2764 | 2765 | _renderGrippers: function() { 2766 | 2767 | var gripper = this.svg.selectAll("path.gripper") 2768 | .data([null]); 2769 | 2770 | gripper.enter() 2771 | .append("path") 2772 | .attr("stroke", this.config.gripperColor) 2773 | .classed("gripper", true); 2774 | 2775 | var path = ""; 2776 | 2777 | [0.4, 0.6].forEach(function(spacing) { 2778 | path += " M " + Math.round((this.currentFrame[0] + (this.config.frameHandleThickness * spacing))) + " " + Math.round(this.config.height * 0.3); 2779 | path += " V " + Math.round(this.config.height * 0.7); 2780 | path += " M " + Math.round((this.currentFrame[1] + (this.config.frameHandleThickness * (1 + spacing)))) + " " + Math.round(this.config.height * 0.3); 2781 | path += " V " + Math.round(this.config.height * 0.7); 2782 | }.bind(this)); 2783 | 2784 | gripper.attr("d", path); 2785 | }, 2786 | 2787 | _renderHandles: function() { 2788 | 2789 | var leftHandle = this.svg.selectAll("rect.left_handle") 2790 | .data([null]); 2791 | 2792 | leftHandle.enter() 2793 | .append("rect") 2794 | .attr('width', this.config.frameHandleThickness) 2795 | .style("cursor", "ew-resize") 2796 | .style("fill-opacity", "0") 2797 | .classed("left_handle", true); 2798 | 2799 | leftHandle 2800 | .attr('x', this.currentFrame[0]) 2801 | .attr('height', this.config.height); 2802 | 2803 | var rightHandle = this.svg.selectAll("rect.right_handle") 2804 | .data([null]); 2805 | 2806 | rightHandle.enter() 2807 | .append("rect") 2808 | .attr('width', this.config.frameHandleThickness) 2809 | .style("cursor", "ew-resize") 2810 | .style("fill-opacity", "0") 2811 | .classed("right_handle", true); 2812 | 2813 | rightHandle 2814 | .attr('x', this.currentFrame[1] + this.config.frameHandleThickness) 2815 | .attr('height', this.config.height); 2816 | }, 2817 | 2818 | _renderMiddle: function() { 2819 | 2820 | var middleHandle = this.svg.selectAll("rect.middle_handle") 2821 | .data([null]); 2822 | 2823 | middleHandle.enter() 2824 | .append("rect") 2825 | .style("cursor", "move") 2826 | .style("fill-opacity", "0") 2827 | .classed("middle_handle", true); 2828 | 2829 | middleHandle 2830 | .attr('width', Math.max(0, this.currentFrame[1] - this.currentFrame[0])) 2831 | .attr('x', this.currentFrame[0] + this.config.frameHandleThickness) 2832 | .attr('height', this.config.height); 2833 | }, 2834 | 2835 | _registerMouseEvents: function() { 2836 | 2837 | var element = d3.select(this.element); 2838 | 2839 | var drag = { 2840 | target: null, 2841 | start: null, 2842 | stop: null, 2843 | left: false, 2844 | right: false, 2845 | rigid: false 2846 | }; 2847 | 2848 | var self = this; 2849 | 2850 | function onMousemove(datum, index) { 2851 | 2852 | drag.stop = self._getClientXFromEvent(d3.event, drag); 2853 | var distanceTraveled = drag.stop - drag.start; 2854 | var frameAfterDrag = self.frameBeforeDrag.slice(0); 2855 | var minimumFrameWidth = self.config.minimumFrameWidth; 2856 | 2857 | if (drag.rigid) { 2858 | minimumFrameWidth = self.frameBeforeDrag[1] - self.frameBeforeDrag[0]; 2859 | } 2860 | if (drag.left) { 2861 | frameAfterDrag[0] = Math.max(frameAfterDrag[0] + distanceTraveled, 0); 2862 | } 2863 | if (drag.right) { 2864 | frameAfterDrag[1] = Math.min(frameAfterDrag[1] + distanceTraveled, self.previewWidth); 2865 | } 2866 | 2867 | var currentFrameWidth = frameAfterDrag[1] - frameAfterDrag[0]; 2868 | 2869 | if (currentFrameWidth <= minimumFrameWidth) { 2870 | 2871 | if (drag.left) { 2872 | frameAfterDrag[0] = frameAfterDrag[1] - minimumFrameWidth; 2873 | } 2874 | if (drag.right) { 2875 | frameAfterDrag[1] = frameAfterDrag[0] + minimumFrameWidth; 2876 | } 2877 | if (frameAfterDrag[0] <= 0) { 2878 | frameAfterDrag[1] -= frameAfterDrag[0]; 2879 | frameAfterDrag[0] = 0; 2880 | } 2881 | if (frameAfterDrag[1] >= self.previewWidth) { 2882 | frameAfterDrag[0] -= (frameAfterDrag[1] - self.previewWidth); 2883 | frameAfterDrag[1] = self.previewWidth; 2884 | } 2885 | } 2886 | 2887 | self.graphs.forEach(function(graph) { 2888 | 2889 | var domainScale = d3.scale.linear() 2890 | .interpolate(d3.interpolateNumber) 2891 | .domain([0, self.previewWidth]) 2892 | .range(graph.dataDomain()); 2893 | 2894 | var windowAfterDrag = [ 2895 | domainScale(frameAfterDrag[0]), 2896 | domainScale(frameAfterDrag[1]) 2897 | ]; 2898 | 2899 | self.slideCallbacks.forEach(function(callback) { 2900 | callback(graph, windowAfterDrag[0], windowAfterDrag[1]); 2901 | }); 2902 | 2903 | if (frameAfterDrag[0] === 0) { 2904 | windowAfterDrag[0] = undefined; 2905 | } 2906 | if (frameAfterDrag[1] === self.previewWidth) { 2907 | windowAfterDrag[1] = undefined; 2908 | } 2909 | graph.window.xMin = windowAfterDrag[0]; 2910 | graph.window.xMax = windowAfterDrag[1]; 2911 | 2912 | graph.update(); 2913 | }); 2914 | } 2915 | 2916 | function onMousedown() { 2917 | drag.target = d3.event.target; 2918 | drag.start = self._getClientXFromEvent(d3.event, drag); 2919 | self.frameBeforeDrag = self.currentFrame.slice(); 2920 | d3.event.preventDefault ? d3.event.preventDefault() : d3.event.returnValue = false; 2921 | d3.select(document).on("mousemove.rickshaw_range_slider_preview", onMousemove); 2922 | d3.select(document).on("mouseup.rickshaw_range_slider_preview", onMouseup); 2923 | d3.select(document).on("touchmove.rickshaw_range_slider_preview", onMousemove); 2924 | d3.select(document).on("touchend.rickshaw_range_slider_preview", onMouseup); 2925 | d3.select(document).on("touchcancel.rickshaw_range_slider_preview", onMouseup); 2926 | } 2927 | 2928 | function onMousedownLeftHandle(datum, index) { 2929 | drag.left = true; 2930 | onMousedown(); 2931 | } 2932 | 2933 | function onMousedownRightHandle(datum, index) { 2934 | drag.right = true; 2935 | onMousedown(); 2936 | } 2937 | 2938 | function onMousedownMiddleHandle(datum, index) { 2939 | drag.left = true; 2940 | drag.right = true; 2941 | drag.rigid = true; 2942 | onMousedown(); 2943 | } 2944 | 2945 | function onMouseup(datum, index) { 2946 | d3.select(document).on("mousemove.rickshaw_range_slider_preview", null); 2947 | d3.select(document).on("mouseup.rickshaw_range_slider_preview", null); 2948 | d3.select(document).on("touchmove.rickshaw_range_slider_preview", null); 2949 | d3.select(document).on("touchend.rickshaw_range_slider_preview", null); 2950 | d3.select(document).on("touchcancel.rickshaw_range_slider_preview", null); 2951 | delete self.frameBeforeDrag; 2952 | drag.left = false; 2953 | drag.right = false; 2954 | drag.rigid = false; 2955 | } 2956 | 2957 | element.select("rect.left_handle").on("mousedown", onMousedownLeftHandle); 2958 | element.select("rect.right_handle").on("mousedown", onMousedownRightHandle); 2959 | element.select("rect.middle_handle").on("mousedown", onMousedownMiddleHandle); 2960 | element.select("rect.left_handle").on("touchstart", onMousedownLeftHandle); 2961 | element.select("rect.right_handle").on("touchstart", onMousedownRightHandle); 2962 | element.select("rect.middle_handle").on("touchstart", onMousedownMiddleHandle); 2963 | }, 2964 | 2965 | _getClientXFromEvent: function(event, drag) { 2966 | 2967 | switch (event.type) { 2968 | case 'touchstart': 2969 | case 'touchmove': 2970 | var touchList = event.changedTouches; 2971 | var touch = null; 2972 | for (var touchIndex = 0; touchIndex < touchList.length; touchIndex++) { 2973 | if (touchList[touchIndex].target === drag.target) { 2974 | touch = touchList[touchIndex]; 2975 | break; 2976 | } 2977 | } 2978 | return touch !== null ? touch.clientX : undefined; 2979 | 2980 | default: 2981 | return event.clientX; 2982 | } 2983 | } 2984 | }); 2985 | 2986 | Rickshaw.namespace("Rickshaw.Graph.Renderer"); 2987 | 2988 | Rickshaw.Graph.Renderer = Rickshaw.Class.create( { 2989 | 2990 | initialize: function(args) { 2991 | this.graph = args.graph; 2992 | this.tension = args.tension || this.tension; 2993 | this.configure(args); 2994 | }, 2995 | 2996 | seriesPathFactory: function() { 2997 | //implement in subclass 2998 | }, 2999 | 3000 | seriesStrokeFactory: function() { 3001 | // implement in subclass 3002 | }, 3003 | 3004 | defaults: function() { 3005 | return { 3006 | tension: 0.8, 3007 | strokeWidth: 2, 3008 | unstack: true, 3009 | padding: { top: 0.01, right: 0, bottom: 0.01, left: 0 }, 3010 | stroke: false, 3011 | fill: false 3012 | }; 3013 | }, 3014 | 3015 | domain: function(data) { 3016 | // Requires that at least one series contains some data 3017 | var stackedData = data || this.graph.stackedData || this.graph.stackData(); 3018 | 3019 | var xMin = +Infinity; 3020 | var xMax = -Infinity; 3021 | 3022 | var yMin = +Infinity; 3023 | var yMax = -Infinity; 3024 | 3025 | stackedData.forEach( function(series) { 3026 | 3027 | series.forEach( function(d) { 3028 | 3029 | if (d.y == null) return; 3030 | 3031 | var y = d.y + d.y0; 3032 | 3033 | if (y < yMin) yMin = y; 3034 | if (y > yMax) yMax = y; 3035 | } ); 3036 | 3037 | if (!series.length) return; 3038 | 3039 | if (series[0].x < xMin) xMin = series[0].x; 3040 | if (series[series.length - 1].x > xMax) xMax = series[series.length - 1].x; 3041 | } ); 3042 | 3043 | xMin -= (xMax - xMin) * this.padding.left; 3044 | xMax += (xMax - xMin) * this.padding.right; 3045 | 3046 | yMin = this.graph.min === 'auto' ? yMin : this.graph.min || 0; 3047 | yMax = this.graph.max === undefined ? yMax : this.graph.max; 3048 | 3049 | if (this.graph.min === 'auto' || yMin < 0) { 3050 | yMin -= (yMax - yMin) * this.padding.bottom; 3051 | } 3052 | 3053 | if (this.graph.max === undefined) { 3054 | yMax += (yMax - yMin) * this.padding.top; 3055 | } 3056 | 3057 | return { x: [xMin, xMax], y: [yMin, yMax] }; 3058 | }, 3059 | 3060 | render: function(args) { 3061 | 3062 | args = args || {}; 3063 | 3064 | var graph = this.graph; 3065 | var series = args.series || graph.series; 3066 | 3067 | var vis = args.vis || graph.vis; 3068 | vis.selectAll('*').remove(); 3069 | 3070 | var data = series 3071 | .filter(function(s) { return !s.disabled }) 3072 | .map(function(s) { return s.stack }); 3073 | 3074 | var pathNodes = vis.selectAll("path.path") 3075 | .data(data) 3076 | .enter().append("svg:path") 3077 | .classed('path', true) 3078 | .attr("d", this.seriesPathFactory()); 3079 | 3080 | if (this.stroke) { 3081 | var strokeNodes = vis.selectAll('path.stroke') 3082 | .data(data) 3083 | .enter().append("svg:path") 3084 | .classed('stroke', true) 3085 | .attr("d", this.seriesStrokeFactory()); 3086 | } 3087 | 3088 | var i = 0; 3089 | series.forEach( function(series) { 3090 | if (series.disabled) return; 3091 | series.path = pathNodes[0][i]; 3092 | if (this.stroke) series.stroke = strokeNodes[0][i]; 3093 | this._styleSeries(series); 3094 | i++; 3095 | }, this ); 3096 | 3097 | }, 3098 | 3099 | _styleSeries: function(series) { 3100 | 3101 | var fill = this.fill ? series.color : 'none'; 3102 | var stroke = this.stroke ? series.color : 'none'; 3103 | 3104 | series.path.setAttribute('fill', fill); 3105 | series.path.setAttribute('stroke', stroke); 3106 | series.path.setAttribute('stroke-width', this.strokeWidth); 3107 | 3108 | if (series.className) { 3109 | d3.select(series.path).classed(series.className, true); 3110 | } 3111 | if (series.className && this.stroke) { 3112 | d3.select(series.stroke).classed(series.className, true); 3113 | } 3114 | }, 3115 | 3116 | configure: function(args) { 3117 | 3118 | args = args || {}; 3119 | 3120 | Rickshaw.keys(this.defaults()).forEach( function(key) { 3121 | 3122 | if (!args.hasOwnProperty(key)) { 3123 | this[key] = this[key] || this.graph[key] || this.defaults()[key]; 3124 | return; 3125 | } 3126 | 3127 | if (typeof this.defaults()[key] == 'object') { 3128 | 3129 | Rickshaw.keys(this.defaults()[key]).forEach( function(k) { 3130 | 3131 | this[key][k] = 3132 | args[key][k] !== undefined ? args[key][k] : 3133 | this[key][k] !== undefined ? this[key][k] : 3134 | this.defaults()[key][k]; 3135 | }, this ); 3136 | 3137 | } else { 3138 | this[key] = 3139 | args[key] !== undefined ? args[key] : 3140 | this[key] !== undefined ? this[key] : 3141 | this.graph[key] !== undefined ? this.graph[key] : 3142 | this.defaults()[key]; 3143 | } 3144 | 3145 | }, this ); 3146 | }, 3147 | 3148 | setStrokeWidth: function(strokeWidth) { 3149 | if (strokeWidth !== undefined) { 3150 | this.strokeWidth = strokeWidth; 3151 | } 3152 | }, 3153 | 3154 | setTension: function(tension) { 3155 | if (tension !== undefined) { 3156 | this.tension = tension; 3157 | } 3158 | } 3159 | } ); 3160 | 3161 | Rickshaw.namespace('Rickshaw.Graph.Renderer.Line'); 3162 | 3163 | Rickshaw.Graph.Renderer.Line = Rickshaw.Class.create( Rickshaw.Graph.Renderer, { 3164 | 3165 | name: 'line', 3166 | 3167 | defaults: function($super) { 3168 | 3169 | return Rickshaw.extend( $super(), { 3170 | unstack: true, 3171 | fill: false, 3172 | stroke: true 3173 | } ); 3174 | }, 3175 | 3176 | seriesPathFactory: function() { 3177 | 3178 | var graph = this.graph; 3179 | 3180 | var factory = d3.svg.line() 3181 | .x( function(d) { return graph.x(d.x) } ) 3182 | .y( function(d) { return graph.y(d.y) } ) 3183 | .interpolate(this.graph.interpolation).tension(this.tension); 3184 | 3185 | factory.defined && factory.defined( function(d) { return d.y !== null } ); 3186 | return factory; 3187 | } 3188 | } ); 3189 | 3190 | Rickshaw.namespace('Rickshaw.Graph.Renderer.Stack'); 3191 | 3192 | Rickshaw.Graph.Renderer.Stack = Rickshaw.Class.create( Rickshaw.Graph.Renderer, { 3193 | 3194 | name: 'stack', 3195 | 3196 | defaults: function($super) { 3197 | 3198 | return Rickshaw.extend( $super(), { 3199 | fill: true, 3200 | stroke: false, 3201 | unstack: false 3202 | } ); 3203 | }, 3204 | 3205 | seriesPathFactory: function() { 3206 | 3207 | var graph = this.graph; 3208 | 3209 | var factory = d3.svg.area() 3210 | .x( function(d) { return graph.x(d.x) } ) 3211 | .y0( function(d) { return graph.y(d.y0) } ) 3212 | .y1( function(d) { return graph.y(d.y + d.y0) } ) 3213 | .interpolate(this.graph.interpolation).tension(this.tension); 3214 | 3215 | factory.defined && factory.defined( function(d) { return d.y !== null } ); 3216 | return factory; 3217 | } 3218 | } ); 3219 | 3220 | Rickshaw.namespace('Rickshaw.Graph.Renderer.Bar'); 3221 | 3222 | Rickshaw.Graph.Renderer.Bar = Rickshaw.Class.create( Rickshaw.Graph.Renderer, { 3223 | 3224 | name: 'bar', 3225 | 3226 | defaults: function($super) { 3227 | 3228 | var defaults = Rickshaw.extend( $super(), { 3229 | gapSize: 0.05, 3230 | unstack: false 3231 | } ); 3232 | 3233 | delete defaults.tension; 3234 | return defaults; 3235 | }, 3236 | 3237 | initialize: function($super, args) { 3238 | args = args || {}; 3239 | this.gapSize = args.gapSize || this.gapSize; 3240 | $super(args); 3241 | }, 3242 | 3243 | domain: function($super) { 3244 | 3245 | var domain = $super(); 3246 | 3247 | var frequentInterval = this._frequentInterval(this.graph.stackedData.slice(-1).shift()); 3248 | domain.x[1] += Number(frequentInterval.magnitude); 3249 | 3250 | return domain; 3251 | }, 3252 | 3253 | barWidth: function(series) { 3254 | 3255 | var frequentInterval = this._frequentInterval(series.stack); 3256 | var barWidth = this.graph.x.magnitude(frequentInterval.magnitude) * (1 - this.gapSize); 3257 | 3258 | return barWidth; 3259 | }, 3260 | 3261 | render: function(args) { 3262 | 3263 | args = args || {}; 3264 | 3265 | var graph = this.graph; 3266 | var series = args.series || graph.series; 3267 | 3268 | var vis = args.vis || graph.vis; 3269 | vis.selectAll('*').remove(); 3270 | 3271 | var barWidth = this.barWidth(series.active()[0]); 3272 | var barXOffset = 0; 3273 | 3274 | var activeSeriesCount = series.filter( function(s) { return !s.disabled; } ).length; 3275 | var seriesBarWidth = this.unstack ? barWidth / activeSeriesCount : barWidth; 3276 | 3277 | var transform = function(d) { 3278 | // add a matrix transform for negative values 3279 | var matrix = [ 1, 0, 0, (d.y < 0 ? -1 : 1), 0, (d.y < 0 ? graph.y.magnitude(Math.abs(d.y)) * 2 : 0) ]; 3280 | return "matrix(" + matrix.join(',') + ")"; 3281 | }; 3282 | 3283 | series.forEach( function(series) { 3284 | 3285 | if (series.disabled) return; 3286 | 3287 | var barWidth = this.barWidth(series); 3288 | 3289 | var nodes = vis.selectAll("path") 3290 | .data(series.stack.filter( function(d) { return d.y !== null } )) 3291 | .enter().append("svg:rect") 3292 | .attr("x", function(d) { return graph.x(d.x) + barXOffset }) 3293 | .attr("y", function(d) { return (graph.y(d.y0 + Math.abs(d.y))) * (d.y < 0 ? -1 : 1 ) }) 3294 | .attr("width", seriesBarWidth) 3295 | .attr("height", function(d) { return graph.y.magnitude(Math.abs(d.y)) }) 3296 | .attr("transform", transform); 3297 | 3298 | Array.prototype.forEach.call(nodes[0], function(n) { 3299 | n.setAttribute('fill', series.color); 3300 | } ); 3301 | 3302 | if (this.unstack) barXOffset += seriesBarWidth; 3303 | 3304 | }, this ); 3305 | }, 3306 | 3307 | _frequentInterval: function(data) { 3308 | 3309 | var intervalCounts = {}; 3310 | 3311 | for (var i = 0; i < data.length - 1; i++) { 3312 | var interval = data[i + 1].x - data[i].x; 3313 | intervalCounts[interval] = intervalCounts[interval] || 0; 3314 | intervalCounts[interval]++; 3315 | } 3316 | 3317 | var frequentInterval = { count: 0, magnitude: 1 }; 3318 | 3319 | Rickshaw.keys(intervalCounts).forEach( function(i) { 3320 | if (frequentInterval.count < intervalCounts[i]) { 3321 | frequentInterval = { 3322 | count: intervalCounts[i], 3323 | magnitude: i 3324 | }; 3325 | } 3326 | } ); 3327 | 3328 | return frequentInterval; 3329 | } 3330 | } ); 3331 | 3332 | Rickshaw.namespace('Rickshaw.Graph.Renderer.Area'); 3333 | 3334 | Rickshaw.Graph.Renderer.Area = Rickshaw.Class.create( Rickshaw.Graph.Renderer, { 3335 | 3336 | name: 'area', 3337 | 3338 | defaults: function($super) { 3339 | 3340 | return Rickshaw.extend( $super(), { 3341 | unstack: false, 3342 | fill: false, 3343 | stroke: false 3344 | } ); 3345 | }, 3346 | 3347 | seriesPathFactory: function() { 3348 | 3349 | var graph = this.graph; 3350 | 3351 | var factory = d3.svg.area() 3352 | .x( function(d) { return graph.x(d.x) } ) 3353 | .y0( function(d) { return graph.y(d.y0) } ) 3354 | .y1( function(d) { return graph.y(d.y + d.y0) } ) 3355 | .interpolate(graph.interpolation).tension(this.tension); 3356 | 3357 | factory.defined && factory.defined( function(d) { return d.y !== null } ); 3358 | return factory; 3359 | }, 3360 | 3361 | seriesStrokeFactory: function() { 3362 | 3363 | var graph = this.graph; 3364 | 3365 | var factory = d3.svg.line() 3366 | .x( function(d) { return graph.x(d.x) } ) 3367 | .y( function(d) { return graph.y(d.y + d.y0) } ) 3368 | .interpolate(graph.interpolation).tension(this.tension); 3369 | 3370 | factory.defined && factory.defined( function(d) { return d.y !== null } ); 3371 | return factory; 3372 | }, 3373 | 3374 | render: function(args) { 3375 | 3376 | args = args || {}; 3377 | 3378 | var graph = this.graph; 3379 | var series = args.series || graph.series; 3380 | 3381 | var vis = args.vis || graph.vis; 3382 | vis.selectAll('*').remove(); 3383 | 3384 | // insert or stacked areas so strokes lay on top of areas 3385 | var method = this.unstack ? 'append' : 'insert'; 3386 | 3387 | var data = series 3388 | .filter(function(s) { return !s.disabled }) 3389 | .map(function(s) { return s.stack }); 3390 | 3391 | var nodes = vis.selectAll("path") 3392 | .data(data) 3393 | .enter()[method]("svg:g", 'g'); 3394 | 3395 | nodes.append("svg:path") 3396 | .attr("d", this.seriesPathFactory()) 3397 | .attr("class", 'area'); 3398 | 3399 | if (this.stroke) { 3400 | nodes.append("svg:path") 3401 | .attr("d", this.seriesStrokeFactory()) 3402 | .attr("class", 'line'); 3403 | } 3404 | 3405 | var i = 0; 3406 | series.forEach( function(series) { 3407 | if (series.disabled) return; 3408 | series.path = nodes[0][i++]; 3409 | this._styleSeries(series); 3410 | }, this ); 3411 | }, 3412 | 3413 | _styleSeries: function(series) { 3414 | 3415 | if (!series.path) return; 3416 | 3417 | d3.select(series.path).select('.area') 3418 | .attr('fill', series.color); 3419 | 3420 | if (this.stroke) { 3421 | d3.select(series.path).select('.line') 3422 | .attr('fill', 'none') 3423 | .attr('stroke', series.stroke || d3.interpolateRgb(series.color, 'black')(0.125)) 3424 | .attr('stroke-width', this.strokeWidth); 3425 | } 3426 | 3427 | if (series.className) { 3428 | series.path.setAttribute('class', series.className); 3429 | } 3430 | } 3431 | } ); 3432 | 3433 | Rickshaw.namespace('Rickshaw.Graph.Renderer.ScatterPlot'); 3434 | 3435 | Rickshaw.Graph.Renderer.ScatterPlot = Rickshaw.Class.create( Rickshaw.Graph.Renderer, { 3436 | 3437 | name: 'scatterplot', 3438 | 3439 | defaults: function($super) { 3440 | 3441 | return Rickshaw.extend( $super(), { 3442 | unstack: true, 3443 | fill: true, 3444 | stroke: false, 3445 | padding:{ top: 0.01, right: 0.01, bottom: 0.01, left: 0.01 }, 3446 | dotSize: 4 3447 | } ); 3448 | }, 3449 | 3450 | initialize: function($super, args) { 3451 | $super(args); 3452 | }, 3453 | 3454 | render: function(args) { 3455 | 3456 | args = args || {}; 3457 | 3458 | var graph = this.graph; 3459 | 3460 | var series = args.series || graph.series; 3461 | var vis = args.vis || graph.vis; 3462 | 3463 | var dotSize = this.dotSize; 3464 | 3465 | vis.selectAll('*').remove(); 3466 | 3467 | series.forEach( function(series) { 3468 | 3469 | if (series.disabled) return; 3470 | 3471 | var nodes = vis.selectAll("path") 3472 | .data(series.stack.filter( function(d) { return d.y !== null } )) 3473 | .enter().append("svg:circle") 3474 | .attr("cx", function(d) { return graph.x(d.x) }) 3475 | .attr("cy", function(d) { return graph.y(d.y) }) 3476 | .attr("r", function(d) { return ("r" in d) ? d.r : dotSize}); 3477 | if (series.className) { 3478 | nodes.classed(series.className, true); 3479 | } 3480 | 3481 | Array.prototype.forEach.call(nodes[0], function(n) { 3482 | n.setAttribute('fill', series.color); 3483 | } ); 3484 | 3485 | }, this ); 3486 | } 3487 | } ); 3488 | Rickshaw.namespace('Rickshaw.Graph.Renderer.Multi'); 3489 | 3490 | Rickshaw.Graph.Renderer.Multi = Rickshaw.Class.create( Rickshaw.Graph.Renderer, { 3491 | 3492 | name: 'multi', 3493 | 3494 | initialize: function($super, args) { 3495 | 3496 | $super(args); 3497 | }, 3498 | 3499 | defaults: function($super) { 3500 | 3501 | return Rickshaw.extend( $super(), { 3502 | unstack: true, 3503 | fill: false, 3504 | stroke: true 3505 | } ); 3506 | }, 3507 | 3508 | configure: function($super, args) { 3509 | 3510 | args = args || {}; 3511 | this.config = args; 3512 | $super(args); 3513 | }, 3514 | 3515 | domain: function($super) { 3516 | 3517 | this.graph.stackData(); 3518 | 3519 | var domains = []; 3520 | 3521 | var groups = this._groups(); 3522 | this._stack(groups); 3523 | 3524 | groups.forEach( function(group) { 3525 | 3526 | var data = group.series 3527 | .filter( function(s) { return !s.disabled } ) 3528 | .map( function(s) { return s.stack }); 3529 | 3530 | if (!data.length) return; 3531 | 3532 | var domain = null; 3533 | if (group.renderer && group.renderer.domain) { 3534 | domain = group.renderer.domain(data); 3535 | } 3536 | else { 3537 | domain = $super(data); 3538 | } 3539 | domains.push(domain); 3540 | }); 3541 | 3542 | var xMin = d3.min(domains.map( function(d) { return d.x[0] } )); 3543 | var xMax = d3.max(domains.map( function(d) { return d.x[1] } )); 3544 | var yMin = d3.min(domains.map( function(d) { return d.y[0] } )); 3545 | var yMax = d3.max(domains.map( function(d) { return d.y[1] } )); 3546 | 3547 | return { x: [xMin, xMax], y: [yMin, yMax] }; 3548 | }, 3549 | 3550 | _groups: function() { 3551 | 3552 | var graph = this.graph; 3553 | 3554 | var renderGroups = {}; 3555 | 3556 | graph.series.forEach( function(series) { 3557 | 3558 | if (series.disabled) return; 3559 | 3560 | if (!renderGroups[series.renderer]) { 3561 | 3562 | var ns = "http://www.w3.org/2000/svg"; 3563 | var vis = document.createElementNS(ns, 'g'); 3564 | 3565 | graph.vis[0][0].appendChild(vis); 3566 | 3567 | var renderer = graph._renderers[series.renderer]; 3568 | 3569 | var config = {}; 3570 | 3571 | var defaults = [ this.defaults(), renderer.defaults(), this.config, this.graph ]; 3572 | defaults.forEach(function(d) { Rickshaw.extend(config, d) }); 3573 | 3574 | renderer.configure(config); 3575 | 3576 | renderGroups[series.renderer] = { 3577 | renderer: renderer, 3578 | series: [], 3579 | vis: d3.select(vis) 3580 | }; 3581 | } 3582 | 3583 | renderGroups[series.renderer].series.push(series); 3584 | 3585 | }, this); 3586 | 3587 | var groups = []; 3588 | 3589 | Object.keys(renderGroups).forEach( function(key) { 3590 | var group = renderGroups[key]; 3591 | groups.push(group); 3592 | }); 3593 | 3594 | return groups; 3595 | }, 3596 | 3597 | _stack: function(groups) { 3598 | 3599 | groups.forEach( function(group) { 3600 | 3601 | var series = group.series 3602 | .filter( function(series) { return !series.disabled } ); 3603 | 3604 | var data = series 3605 | .map( function(series) { return series.stack } ); 3606 | 3607 | if (!group.renderer.unstack) { 3608 | 3609 | var layout = d3.layout.stack(); 3610 | var stackedData = Rickshaw.clone(layout(data)); 3611 | 3612 | series.forEach( function(series, index) { 3613 | series._stack = Rickshaw.clone(stackedData[index]); 3614 | }); 3615 | } 3616 | 3617 | }, this ); 3618 | 3619 | return groups; 3620 | 3621 | }, 3622 | 3623 | render: function() { 3624 | 3625 | this.graph.series.forEach( function(series) { 3626 | if (!series.renderer) { 3627 | throw new Error("Each series needs a renderer for graph 'multi' renderer"); 3628 | } 3629 | }); 3630 | 3631 | this.graph.vis.selectAll('*').remove(); 3632 | 3633 | var groups = this._groups(); 3634 | groups = this._stack(groups); 3635 | 3636 | groups.forEach( function(group) { 3637 | 3638 | var series = group.series 3639 | .filter( function(series) { return !series.disabled } ); 3640 | 3641 | series.active = function() { return series }; 3642 | 3643 | group.renderer.render({ series: series, vis: group.vis }); 3644 | series.forEach(function(s) { s.stack = s._stack || s.stack || s.data; }); 3645 | }); 3646 | } 3647 | 3648 | } ); 3649 | Rickshaw.namespace('Rickshaw.Graph.Renderer.LinePlot'); 3650 | 3651 | Rickshaw.Graph.Renderer.LinePlot = Rickshaw.Class.create( Rickshaw.Graph.Renderer, { 3652 | 3653 | name: 'lineplot', 3654 | 3655 | defaults: function($super) { 3656 | 3657 | return Rickshaw.extend( $super(), { 3658 | unstack: true, 3659 | fill: false, 3660 | stroke: true, 3661 | padding:{ top: 0.01, right: 0.01, bottom: 0.01, left: 0.01 }, 3662 | dotSize: 3, 3663 | strokeWidth: 2 3664 | } ); 3665 | }, 3666 | 3667 | seriesPathFactory: function() { 3668 | 3669 | var graph = this.graph; 3670 | 3671 | var factory = d3.svg.line() 3672 | .x( function(d) { return graph.x(d.x) } ) 3673 | .y( function(d) { return graph.y(d.y) } ) 3674 | .interpolate(this.graph.interpolation).tension(this.tension); 3675 | 3676 | factory.defined && factory.defined( function(d) { return d.y !== null } ); 3677 | return factory; 3678 | }, 3679 | 3680 | render: function(args) { 3681 | 3682 | args = args || {}; 3683 | 3684 | var graph = this.graph; 3685 | 3686 | var series = args.series || graph.series; 3687 | var vis = args.vis || graph.vis; 3688 | 3689 | var dotSize = this.dotSize; 3690 | 3691 | vis.selectAll('*').remove(); 3692 | 3693 | var data = series 3694 | .filter(function(s) { return !s.disabled }) 3695 | .map(function(s) { return s.stack }); 3696 | 3697 | var nodes = vis.selectAll("path") 3698 | .data(data) 3699 | .enter().append("svg:path") 3700 | .attr("d", this.seriesPathFactory()); 3701 | 3702 | var i = 0; 3703 | series.forEach(function(series) { 3704 | if (series.disabled) return; 3705 | series.path = nodes[0][i++]; 3706 | this._styleSeries(series); 3707 | }, this); 3708 | 3709 | series.forEach(function(series) { 3710 | 3711 | if (series.disabled) return; 3712 | 3713 | var nodes = vis.selectAll("x") 3714 | .data(series.stack.filter( function(d) { return d.y !== null } )) 3715 | .enter().append("svg:circle") 3716 | .attr("cx", function(d) { return graph.x(d.x) }) 3717 | .attr("cy", function(d) { return graph.y(d.y) }) 3718 | .attr("r", function(d) { return ("r" in d) ? d.r : dotSize}); 3719 | 3720 | Array.prototype.forEach.call(nodes[0], function(n) { 3721 | if (!n) return; 3722 | n.setAttribute('data-color', series.color); 3723 | n.setAttribute('fill', 'white'); 3724 | n.setAttribute('stroke', series.color); 3725 | n.setAttribute('stroke-width', this.strokeWidth); 3726 | 3727 | }.bind(this)); 3728 | 3729 | }, this); 3730 | } 3731 | } ); 3732 | 3733 | Rickshaw.namespace('Rickshaw.Graph.Smoother'); 3734 | 3735 | Rickshaw.Graph.Smoother = Rickshaw.Class.create({ 3736 | 3737 | initialize: function(args) { 3738 | 3739 | this.graph = args.graph; 3740 | this.element = args.element; 3741 | this.aggregationScale = 1; 3742 | 3743 | this.build(); 3744 | 3745 | this.graph.stackData.hooks.data.push( { 3746 | name: 'smoother', 3747 | orderPosition: 50, 3748 | f: this.transformer.bind(this) 3749 | } ); 3750 | }, 3751 | 3752 | build: function() { 3753 | 3754 | var self = this; 3755 | var $ = jQuery; 3756 | 3757 | if (this.element) { 3758 | $( function() { 3759 | $(self.element).slider( { 3760 | min: 1, 3761 | max: 100, 3762 | slide: function( event, ui ) { 3763 | self.setScale(ui.value); 3764 | } 3765 | } ); 3766 | } ); 3767 | } 3768 | }, 3769 | 3770 | setScale: function(scale) { 3771 | 3772 | if (scale < 1) { 3773 | throw "scale out of range: " + scale; 3774 | } 3775 | 3776 | this.aggregationScale = scale; 3777 | this.graph.update(); 3778 | }, 3779 | 3780 | transformer: function(data) { 3781 | 3782 | if (this.aggregationScale == 1) return data; 3783 | 3784 | var aggregatedData = []; 3785 | 3786 | data.forEach( function(seriesData) { 3787 | 3788 | var aggregatedSeriesData = []; 3789 | 3790 | while (seriesData.length) { 3791 | 3792 | var avgX = 0, avgY = 0; 3793 | var slice = seriesData.splice(0, this.aggregationScale); 3794 | 3795 | slice.forEach( function(d) { 3796 | avgX += d.x / slice.length; 3797 | avgY += d.y / slice.length; 3798 | } ); 3799 | 3800 | aggregatedSeriesData.push( { x: avgX, y: avgY } ); 3801 | } 3802 | 3803 | aggregatedData.push(aggregatedSeriesData); 3804 | 3805 | }.bind(this) ); 3806 | 3807 | return aggregatedData; 3808 | } 3809 | }); 3810 | 3811 | Rickshaw.namespace('Rickshaw.Graph.Socketio'); 3812 | 3813 | Rickshaw.Graph.Socketio = Rickshaw.Class.create( Rickshaw.Graph.Ajax, { 3814 | request: function() { 3815 | var socket = io.connect(this.dataURL); 3816 | var self = this; 3817 | socket.on('rickshaw', function (data) { 3818 | self.success(data); 3819 | }); 3820 | } 3821 | } ); 3822 | Rickshaw.namespace('Rickshaw.Series'); 3823 | 3824 | Rickshaw.Series = Rickshaw.Class.create( Array, { 3825 | 3826 | initialize: function (data, palette, options) { 3827 | 3828 | options = options || {}; 3829 | 3830 | this.palette = new Rickshaw.Color.Palette(palette); 3831 | 3832 | this.timeBase = typeof(options.timeBase) === 'undefined' ? 3833 | Math.floor(new Date().getTime() / 1000) : 3834 | options.timeBase; 3835 | 3836 | var timeInterval = typeof(options.timeInterval) == 'undefined' ? 3837 | 1000 : 3838 | options.timeInterval; 3839 | 3840 | this.setTimeInterval(timeInterval); 3841 | 3842 | if (data && (typeof(data) == "object") && Array.isArray(data)) { 3843 | data.forEach( function(item) { this.addItem(item) }, this ); 3844 | } 3845 | }, 3846 | 3847 | addItem: function(item) { 3848 | 3849 | if (typeof(item.name) === 'undefined') { 3850 | throw('addItem() needs a name'); 3851 | } 3852 | 3853 | item.color = (item.color || this.palette.color(item.name)); 3854 | item.data = (item.data || []); 3855 | 3856 | // backfill, if necessary 3857 | if ((item.data.length === 0) && this.length && (this.getIndex() > 0)) { 3858 | this[0].data.forEach( function(plot) { 3859 | item.data.push({ x: plot.x, y: 0 }); 3860 | } ); 3861 | } else if (item.data.length === 0) { 3862 | item.data.push({ x: this.timeBase - (this.timeInterval || 0), y: 0 }); 3863 | } 3864 | 3865 | this.push(item); 3866 | 3867 | if (this.legend) { 3868 | this.legend.addLine(this.itemByName(item.name)); 3869 | } 3870 | }, 3871 | 3872 | addData: function(data, x) { 3873 | 3874 | var index = this.getIndex(); 3875 | 3876 | Rickshaw.keys(data).forEach( function(name) { 3877 | if (! this.itemByName(name)) { 3878 | this.addItem({ name: name }); 3879 | } 3880 | }, this ); 3881 | 3882 | this.forEach( function(item) { 3883 | item.data.push({ 3884 | x: x || (index * this.timeInterval || 1) + this.timeBase, 3885 | y: (data[item.name] || 0) 3886 | }); 3887 | }, this ); 3888 | }, 3889 | 3890 | getIndex: function () { 3891 | return (this[0] && this[0].data && this[0].data.length) ? this[0].data.length : 0; 3892 | }, 3893 | 3894 | itemByName: function(name) { 3895 | 3896 | for (var i = 0; i < this.length; i++) { 3897 | if (this[i].name == name) 3898 | return this[i]; 3899 | } 3900 | }, 3901 | 3902 | setTimeInterval: function(iv) { 3903 | this.timeInterval = iv / 1000; 3904 | }, 3905 | 3906 | setTimeBase: function (t) { 3907 | this.timeBase = t; 3908 | }, 3909 | 3910 | dump: function() { 3911 | 3912 | var data = { 3913 | timeBase: this.timeBase, 3914 | timeInterval: this.timeInterval, 3915 | items: [] 3916 | }; 3917 | 3918 | this.forEach( function(item) { 3919 | 3920 | var newItem = { 3921 | color: item.color, 3922 | name: item.name, 3923 | data: [] 3924 | }; 3925 | 3926 | item.data.forEach( function(plot) { 3927 | newItem.data.push({ x: plot.x, y: plot.y }); 3928 | } ); 3929 | 3930 | data.items.push(newItem); 3931 | } ); 3932 | 3933 | return data; 3934 | }, 3935 | 3936 | load: function(data) { 3937 | 3938 | if (data.timeInterval) { 3939 | this.timeInterval = data.timeInterval; 3940 | } 3941 | 3942 | if (data.timeBase) { 3943 | this.timeBase = data.timeBase; 3944 | } 3945 | 3946 | if (data.items) { 3947 | data.items.forEach( function(item) { 3948 | this.push(item); 3949 | if (this.legend) { 3950 | this.legend.addLine(this.itemByName(item.name)); 3951 | } 3952 | 3953 | }, this ); 3954 | } 3955 | } 3956 | } ); 3957 | 3958 | Rickshaw.Series.zeroFill = function(series) { 3959 | Rickshaw.Series.fill(series, 0); 3960 | }; 3961 | 3962 | Rickshaw.Series.fill = function(series, fill) { 3963 | 3964 | var x; 3965 | var i = 0; 3966 | 3967 | var data = series.map( function(s) { return s.data } ); 3968 | 3969 | while ( i < Math.max.apply(null, data.map( function(d) { return d.length } )) ) { 3970 | 3971 | x = Math.min.apply( null, 3972 | data 3973 | .filter(function(d) { return d[i] }) 3974 | .map(function(d) { return d[i].x }) 3975 | ); 3976 | 3977 | data.forEach( function(d) { 3978 | if (!d[i] || d[i].x != x) { 3979 | d.splice(i, 0, { x: x, y: fill }); 3980 | } 3981 | } ); 3982 | 3983 | i++; 3984 | } 3985 | }; 3986 | 3987 | Rickshaw.namespace('Rickshaw.Series.FixedDuration'); 3988 | 3989 | Rickshaw.Series.FixedDuration = Rickshaw.Class.create(Rickshaw.Series, { 3990 | 3991 | initialize: function (data, palette, options) { 3992 | 3993 | options = options || {}; 3994 | 3995 | if (typeof(options.timeInterval) === 'undefined') { 3996 | throw new Error('FixedDuration series requires timeInterval'); 3997 | } 3998 | 3999 | if (typeof(options.maxDataPoints) === 'undefined') { 4000 | throw new Error('FixedDuration series requires maxDataPoints'); 4001 | } 4002 | 4003 | this.palette = new Rickshaw.Color.Palette(palette); 4004 | this.timeBase = typeof(options.timeBase) === 'undefined' ? Math.floor(new Date().getTime() / 1000) : options.timeBase; 4005 | this.setTimeInterval(options.timeInterval); 4006 | 4007 | if (this[0] && this[0].data && this[0].data.length) { 4008 | this.currentSize = this[0].data.length; 4009 | this.currentIndex = this[0].data.length; 4010 | } else { 4011 | this.currentSize = 0; 4012 | this.currentIndex = 0; 4013 | } 4014 | 4015 | this.maxDataPoints = options.maxDataPoints; 4016 | 4017 | 4018 | if (data && (typeof(data) == "object") && Array.isArray(data)) { 4019 | data.forEach( function (item) { this.addItem(item) }, this ); 4020 | this.currentSize += 1; 4021 | this.currentIndex += 1; 4022 | } 4023 | 4024 | // reset timeBase for zero-filled values if needed 4025 | this.timeBase -= (this.maxDataPoints - this.currentSize) * this.timeInterval; 4026 | 4027 | // zero-fill up to maxDataPoints size if we don't have that much data yet 4028 | if ((typeof(this.maxDataPoints) !== 'undefined') && (this.currentSize < this.maxDataPoints)) { 4029 | for (var i = this.maxDataPoints - this.currentSize - 1; i > 1; i--) { 4030 | this.currentSize += 1; 4031 | this.currentIndex += 1; 4032 | this.forEach( function (item) { 4033 | item.data.unshift({ x: ((i-1) * this.timeInterval || 1) + this.timeBase, y: 0, i: i }); 4034 | }, this ); 4035 | } 4036 | } 4037 | }, 4038 | 4039 | addData: function($super, data, x) { 4040 | 4041 | $super(data, x); 4042 | 4043 | this.currentSize += 1; 4044 | this.currentIndex += 1; 4045 | 4046 | if (this.maxDataPoints !== undefined) { 4047 | while (this.currentSize > this.maxDataPoints) { 4048 | this.dropData(); 4049 | } 4050 | } 4051 | }, 4052 | 4053 | dropData: function() { 4054 | 4055 | this.forEach(function(item) { 4056 | item.data.splice(0, 1); 4057 | } ); 4058 | 4059 | this.currentSize -= 1; 4060 | }, 4061 | 4062 | getIndex: function () { 4063 | return this.currentIndex; 4064 | } 4065 | } ); 4066 | 4067 | return Rickshaw; 4068 | })); 4069 | --------------------------------------------------------------------------------