├── .gitignore ├── README.md ├── bin └── tsd-web ├── lib ├── index.js └── websocket.js ├── package.json ├── public ├── .DS_Store ├── css │ └── index.css ├── index.html ├── js │ ├── cubism.v1.js │ ├── d3.v2.js │ ├── highlight.min.js │ ├── index.js │ └── websocket.min.js └── webfonts │ ├── .DS_Store │ ├── ProximaNova-RegItalic.otf │ └── ProximaNova-Regular.otf ├── screenshot.png └── test └── server.js /.gitignore: -------------------------------------------------------------------------------- 1 | /.* 2 | !.gitignore 3 | !.npmignore 4 | /node_modules 5 | /lib/cache/* 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NAME 2 | tsd-web(3) 3 | 4 | # SYNOPSIS 5 | Spin up a quick server to visualize time series data. 6 | 7 | # USAGE 8 | Deadly simple. just specify the ports you want it to listen on. 9 | 10 | ```js 11 | var tsd = require('tsd-web'); 12 | 13 | tsd({ 14 | http: 80, 15 | tcp: 9099 16 | }); 17 | ``` 18 | 19 | Write some dummy data to the socket (LINE DELIMITED!). 20 | ```js 21 | var net = require('net'); 22 | var x = 0; 23 | var client = net.connect({ port: 9099 }, function() { 24 | 25 | function write(json) { 26 | client.write(JSON.stringify(json) + '\n'); 27 | } 28 | 29 | setInterval(function() { 30 | 31 | x++; 32 | 33 | write({ key: 'hello', value: (Math.random() + x) / 50 }); 34 | write({ key: 'goodbye', value: (Math.random() + x) / 20 }); 35 | write({ key: 'ohai', value: 1000 }); 36 | write({ key: 'neat-stuff', value: (Math.random() + x) / 10 }); 37 | 38 | }, 150); 39 | 40 | }); 41 | ``` 42 | 43 | # WUT? 44 | ![screenshot](/screenshot.png) 45 | 46 | # TODO 47 | This is a work in progress. Pull requests welcome. 48 | 49 | - Provide a way to flush the cache. 50 | -------------------------------------------------------------------------------- /bin/tsd-web: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var stshttp = require('../lib'); 4 | var net = require('net'); 5 | 6 | stshttp({ 7 | http: 80, 8 | tcp: 9099 9 | }); 10 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | var http = require('http'); 2 | var net = require('net'); 3 | var url = require('url'); 4 | var fs = require('fs'); 5 | var createCache = require('levelup'); 6 | var path = require('path'); 7 | 8 | var es = require('event-stream'); 9 | 10 | var st = require('st') 11 | var WebSocketServer = require('ws').Server; 12 | var WebSocket = require('./websocket'); 13 | 14 | var staticHandler = st({ 15 | path: path.join(__dirname, '..', 'public'), 16 | url: '/', 17 | index: 'index.html' 18 | }); 19 | 20 | module.exports = function(ports) { 21 | 22 | ports = ports || {}; 23 | 24 | ports.http = ports.http || 80; 25 | ports.tcp = ports.tcp || 9099; 26 | 27 | var location = path.join(__dirname, 'cache'); 28 | 29 | createCache(location, { encoding: 'json' }, function(error, cache) { 30 | 31 | var tcpserver = net.createServer(function(socket) { 32 | 33 | socket 34 | .pipe(es.split()) 35 | .pipe(es.parse()) 36 | .pipe(cache.createWriteStream()) 37 | }); 38 | 39 | tcpserver.listen(ports.tcp, function() { 40 | console.log('tcp server listening on %d', ports.tcp); 41 | }); 42 | 43 | var httpserver = http.createServer(staticHandler); 44 | var wss = new WebSocketServer({ 45 | server: httpserver.listen(ports.http, function() { 46 | console.log('http server listening on %d', ports.http); 47 | }) 48 | }); 49 | 50 | wss.on('connection', function(ws) { 51 | 52 | var websocket = new WebSocket(ws); 53 | 54 | cache.createKeyStream() 55 | .on('data', function(key) { 56 | cache.get(key, function (err, value) { 57 | if (!err) send(key, value); 58 | }); 59 | }) 60 | .on('end', function() { 61 | cache.on('put', send); 62 | }) 63 | 64 | function send(key, value) { 65 | var response = JSON.stringify({ key : key, value : value }); 66 | websocket.write(response); 67 | } 68 | 69 | }); 70 | }); 71 | }; 72 | -------------------------------------------------------------------------------- /lib/websocket.js: -------------------------------------------------------------------------------- 1 | var Stream = require('stream').Stream; 2 | util = require('util') 3 | 4 | function Socket(socket) { 5 | Stream.apply(this, arguments); 6 | 7 | this.socket = socket; 8 | this.readable = true; 9 | this.writable = true; 10 | 11 | var that = this; 12 | 13 | var on = function(name, fn) { 14 | if (socket.on) { 15 | socket.on(name, fn); 16 | } else { 17 | socket['on' + name] = fn; 18 | } 19 | }; 20 | 21 | on('message', function(message) { 22 | if (message && message.srcElement && message instanceof MessageEvent) { 23 | message = message.data; 24 | } 25 | 26 | that.emit('data', message); 27 | }); 28 | 29 | on('close', function() { 30 | that.emit('end'); 31 | }); 32 | 33 | on('error', function(err) { 34 | that.emit('error', err); 35 | }); 36 | 37 | var handleConnection = function() { 38 | that.emit('connection'); 39 | }; 40 | 41 | on('open', handleConnection); 42 | on('connection', handleConnection); 43 | } 44 | 45 | util.inherits(Socket, Stream); 46 | 47 | Socket.prototype.write = function(d) { 48 | try { 49 | if (this.socket.write) { 50 | this.socket.write(d); 51 | } else { 52 | this.socket.send(d); 53 | } 54 | } catch (e) { 55 | this.emit('error', e); 56 | } 57 | }; 58 | 59 | Socket.prototype.end = function() { 60 | this.emit('end'); 61 | }; 62 | 63 | module.exports = Socket; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tsd-web", 3 | "version": "0.1.2", 4 | "description": "spins up a server to receive time-series data via tcp streams and then graphs it in the browser via web-sockets", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "bin" : { 10 | "tsd-web" : "./bin/tsd-web" 11 | }, 12 | "dependencies": { 13 | "ws": "*", 14 | "levelup": "*", 15 | "st": "*", 16 | "event-stream": "*" 17 | }, 18 | "repository": "", 19 | "author": "", 20 | "license": "BSD" 21 | } 22 | -------------------------------------------------------------------------------- /public/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heapwolf/tsd/bcb185745ddc263337f4ff125caa9599e4406323/public/.DS_Store -------------------------------------------------------------------------------- /public/css/index.css: -------------------------------------------------------------------------------- 1 | 2 | 3 | @font-face { 4 | font-family: 'ProximaNova Italic'; 5 | src: url("../webfonts/ProximaNova-RegItalic.otf") format('opentype'); 6 | font-weight: medium; 7 | font-style: normal; 8 | } 9 | @font-face { 10 | font-family: 'ProximaNova Regular'; 11 | src: url("../webfonts/ProximaNova-Regular.otf") format('opentype'); 12 | font-weight: medium; 13 | font-style: normal; 14 | } 15 | 16 | html { 17 | min-width: 1040px; 18 | } 19 | 20 | body { 21 | font-family: 'ProximaNova Regular'; 22 | margin: auto; 23 | margin-top: 40px; 24 | margin-bottom: 4em; 25 | width: 960px; 26 | } 27 | 28 | #body { 29 | position: relative; 30 | } 31 | 32 | footer { 33 | font-size: small; 34 | margin-top: 8em; 35 | } 36 | 37 | aside { 38 | font-size: small; 39 | left: 780px; 40 | position: absolute; 41 | width: 180px; 42 | } 43 | 44 | #body > p, li > p { 45 | line-height: 1.5em; 46 | } 47 | 48 | #body > p { 49 | width: 720px; 50 | } 51 | 52 | #body > blockquote { 53 | width: 640px; 54 | } 55 | 56 | li { 57 | width: 680px; 58 | } 59 | 60 | a { 61 | color: steelblue; 62 | } 63 | 64 | a:not(:hover) { 65 | text-decoration: none; 66 | } 67 | 68 | pre, code, textarea { 69 | font-family: "Menlo", monospace; 70 | } 71 | 72 | code { 73 | line-height: 1em; 74 | } 75 | 76 | textarea { 77 | font-size: 100%; 78 | } 79 | 80 | #body > pre { 81 | border-left: solid 2px #ccc; 82 | padding-left: 18px; 83 | margin: 2em 0 2em -20px; 84 | } 85 | 86 | .html .value, 87 | .javascript .string, 88 | .javascript .regexp { 89 | color: #756bb1; 90 | } 91 | 92 | .html .tag, 93 | .css .tag, 94 | .javascript .keyword { 95 | color: #3182bd; 96 | } 97 | 98 | .comment { 99 | color: #636363; 100 | } 101 | 102 | .html .doctype, 103 | .javascript .number { 104 | color: #31a354; 105 | } 106 | 107 | .html .attribute, 108 | .css .attribute, 109 | .javascript .class, 110 | .javascript .special { 111 | color: #e6550d; 112 | } 113 | 114 | svg { 115 | font: 10px sans-serif; 116 | } 117 | 118 | .axis path, .axis line { 119 | fill: none; 120 | stroke: #000; 121 | shape-rendering: crispEdges; 122 | } 123 | 124 | sup, sub { 125 | line-height: 0; 126 | } 127 | 128 | q:before, 129 | blockquote:before { 130 | content: "“"; 131 | } 132 | 133 | q:after, 134 | blockquote:after { 135 | content: "”"; 136 | } 137 | 138 | blockquote:before { 139 | position: absolute; 140 | left: 2em; 141 | } 142 | 143 | blockquote:after { 144 | position: absolute; 145 | } 146 | 147 | h1 { 148 | font-size: 50px; 149 | margin-top: .3em; 150 | margin-bottom: 0; 151 | } 152 | 153 | h1 + h2 { 154 | margin-top: 0; 155 | } 156 | 157 | h2 { 158 | font-weight: 400; 159 | font-size: 28px; 160 | } 161 | 162 | h1, h2 { 163 | font-family: 'ProximaNova Italic'; 164 | text-rendering: optimizeLegibility; 165 | } 166 | 167 | #logo { 168 | width: 122px; 169 | height: 31px; 170 | } 171 | 172 | #fork { 173 | position: absolute; 174 | top: 0; 175 | right: 0; 176 | } 177 | 178 | .axis { 179 | font: 10px sans-serif; 180 | } 181 | 182 | .axis text { 183 | -webkit-transition: fill-opacity 250ms linear; 184 | } 185 | 186 | .axis path { 187 | display: none; 188 | } 189 | 190 | .axis line { 191 | stroke: #000; 192 | shape-rendering: crispEdges; 193 | } 194 | 195 | .horizon { 196 | border-bottom: solid 1px #000; 197 | overflow: hidden; 198 | position: relative; 199 | height: 125px; 200 | } 201 | 202 | .horizon { 203 | border-top: solid 1px #000; 204 | border-bottom: solid 1px #000; 205 | } 206 | 207 | .horizon + .horizon { 208 | border-top: none; 209 | } 210 | 211 | .horizon canvas { 212 | display: block; 213 | } 214 | 215 | .horizon .title, 216 | .horizon .value { 217 | bottom: 0; 218 | line-height: 30px; 219 | margin: 0 6px; 220 | position: absolute; 221 | text-shadow: 0 1px 0 rgba(255,255,255,.5); 222 | white-space: nowrap; 223 | } 224 | 225 | .horizon .title { 226 | left: 0; 227 | } 228 | 229 | .horizon .value { 230 | right: 0; 231 | } 232 | 233 | .line { 234 | background: #000; 235 | opacity: .2; 236 | z-index: 2; 237 | } 238 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Time Series Data 4 | 5 | 6 | 7 |
8 | 9 |

Time Series Data

10 | 11 |
12 | 13 |

14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /public/js/cubism.v1.js: -------------------------------------------------------------------------------- 1 | (function(exports){ 2 | var cubism = exports.cubism = {version: "1.2.0"}; 3 | var cubism_id = 0; 4 | function cubism_identity(d) { return d; } 5 | cubism.option = function(name, defaultValue) { 6 | var values = cubism.options(name); 7 | return values.length ? values[0] : defaultValue; 8 | }; 9 | 10 | cubism.options = function(name, defaultValues) { 11 | var options = location.search.substring(1).split("&"), 12 | values = [], 13 | i = -1, 14 | n = options.length, 15 | o; 16 | while (++i < n) { 17 | if ((o = options[i].split("="))[0] == name) { 18 | values.push(decodeURIComponent(o[1])); 19 | } 20 | } 21 | return values.length || arguments.length < 2 ? values : defaultValues; 22 | }; 23 | cubism.context = function() { 24 | var context = new cubism_context, 25 | step = 1e4, // ten seconds, in milliseconds 26 | size = 1440, // four hours at ten seconds, in pixels 27 | start0, stop0, // the start and stop for the previous change event 28 | start1, stop1, // the start and stop for the next prepare event 29 | serverDelay = 5e3, 30 | clientDelay = 5e3, 31 | event = d3.dispatch("prepare", "beforechange", "change", "focus"), 32 | scale = context.scale = d3.time.scale().range([0, size]), 33 | timeout, 34 | focus; 35 | 36 | function update() { 37 | var now = Date.now(); 38 | stop0 = new Date(Math.floor((now - serverDelay - clientDelay) / step) * step); 39 | start0 = new Date(stop0 - size * step); 40 | stop1 = new Date(Math.floor((now - serverDelay) / step) * step); 41 | start1 = new Date(stop1 - size * step); 42 | scale.domain([start0, stop0]); 43 | return context; 44 | } 45 | 46 | context.start = function() { 47 | if (timeout) clearTimeout(timeout); 48 | var delay = +stop1 + serverDelay - Date.now(); 49 | 50 | // If we're too late for the first prepare event, skip it. 51 | if (delay < clientDelay) delay += step; 52 | 53 | timeout = setTimeout(function prepare() { 54 | stop1 = new Date(Math.floor((Date.now() - serverDelay) / step) * step); 55 | start1 = new Date(stop1 - size * step); 56 | event.prepare.call(context, start1, stop1); 57 | 58 | setTimeout(function() { 59 | scale.domain([start0 = start1, stop0 = stop1]); 60 | event.beforechange.call(context, start1, stop1); 61 | event.change.call(context, start1, stop1); 62 | event.focus.call(context, focus); 63 | }, clientDelay); 64 | 65 | timeout = setTimeout(prepare, step); 66 | }, delay); 67 | return context; 68 | }; 69 | 70 | context.stop = function() { 71 | timeout = clearTimeout(timeout); 72 | return context; 73 | }; 74 | 75 | timeout = setTimeout(context.start, 10); 76 | 77 | // Set or get the step interval in milliseconds. 78 | // Defaults to ten seconds. 79 | context.step = function(_) { 80 | if (!arguments.length) return step; 81 | step = +_; 82 | return update(); 83 | }; 84 | 85 | // Set or get the context size (the count of metric values). 86 | // Defaults to 1440 (four hours at ten seconds). 87 | context.size = function(_) { 88 | if (!arguments.length) return size; 89 | scale.range([0, size = +_]); 90 | return update(); 91 | }; 92 | 93 | // The server delay is the amount of time we wait for the server to compute a 94 | // metric. This delay may result from clock skew or from delays collecting 95 | // metrics from various hosts. Defaults to 4 seconds. 96 | context.serverDelay = function(_) { 97 | if (!arguments.length) return serverDelay; 98 | serverDelay = +_; 99 | return update(); 100 | }; 101 | 102 | // The client delay is the amount of additional time we wait to fetch those 103 | // metrics from the server. The client and server delay combined represent the 104 | // age of the most recent displayed metric. Defaults to 1 second. 105 | context.clientDelay = function(_) { 106 | if (!arguments.length) return clientDelay; 107 | clientDelay = +_; 108 | return update(); 109 | }; 110 | 111 | // Sets the focus to the specified index, and dispatches a "focus" event. 112 | context.focus = function(i) { 113 | event.focus.call(context, focus = i); 114 | return context; 115 | }; 116 | 117 | // Add, remove or get listeners for events. 118 | context.on = function(type, listener) { 119 | if (arguments.length < 2) return event.on(type); 120 | 121 | event.on(type, listener); 122 | 123 | // Notify the listener of the current start and stop time, as appropriate. 124 | // This way, metrics can make requests for data immediately, 125 | // and likewise the axis can display itself synchronously. 126 | if (listener != null) { 127 | if (/^prepare(\.|$)/.test(type)) listener.call(context, start1, stop1); 128 | if (/^beforechange(\.|$)/.test(type)) listener.call(context, start0, stop0); 129 | if (/^change(\.|$)/.test(type)) listener.call(context, start0, stop0); 130 | if (/^focus(\.|$)/.test(type)) listener.call(context, focus); 131 | } 132 | 133 | return context; 134 | }; 135 | 136 | d3.select(window).on("keydown.context-" + ++cubism_id, function() { 137 | switch (!d3.event.metaKey && d3.event.keyCode) { 138 | case 37: // left 139 | if (focus == null) focus = size - 1; 140 | if (focus > 0) context.focus(--focus); 141 | break; 142 | case 39: // right 143 | if (focus == null) focus = size - 2; 144 | if (focus < size - 1) context.focus(++focus); 145 | break; 146 | default: return; 147 | } 148 | d3.event.preventDefault(); 149 | }); 150 | 151 | return update(); 152 | }; 153 | 154 | function cubism_context() {} 155 | 156 | var cubism_contextPrototype = cubism.context.prototype = cubism_context.prototype; 157 | 158 | cubism_contextPrototype.constant = function(value) { 159 | return new cubism_metricConstant(this, +value); 160 | }; 161 | cubism_contextPrototype.cube = function(host) { 162 | if (!arguments.length) host = ""; 163 | var source = {}, 164 | context = this; 165 | 166 | source.metric = function(expression) { 167 | return context.metric(function(start, stop, step, callback) { 168 | d3.json(host + "/1.0/metric" 169 | + "?expression=" + encodeURIComponent(expression) 170 | + "&start=" + cubism_cubeFormatDate(start) 171 | + "&stop=" + cubism_cubeFormatDate(stop) 172 | + "&step=" + step, function(data) { 173 | if (!data) return callback(new Error("unable to load data")); 174 | callback(null, data.map(function(d) { return d.value; })); 175 | }); 176 | }, expression += ""); 177 | }; 178 | 179 | // Returns the Cube host. 180 | source.toString = function() { 181 | return host; 182 | }; 183 | 184 | return source; 185 | }; 186 | 187 | var cubism_cubeFormatDate = d3.time.format.iso; 188 | cubism_contextPrototype.graphite = function(host) { 189 | if (!arguments.length) host = ""; 190 | var source = {}, 191 | context = this; 192 | 193 | source.metric = function(expression) { 194 | var sum = "sum"; 195 | 196 | var metric = context.metric(function(start, stop, step, callback) { 197 | var target = expression; 198 | 199 | // Apply the summarize, if necessary. 200 | if (step !== 1e4) target = "summarize(" + target + ",'" 201 | + (!(step % 36e5) ? step / 36e5 + "hour" : !(step % 6e4) ? step / 6e4 + "min" : step + "sec") 202 | + "','" + sum + "')"; 203 | 204 | d3.text(host + "/render?format=raw" 205 | + "&target=" + encodeURIComponent("alias(" + target + ",'')") 206 | + "&from=" + cubism_graphiteFormatDate(start - 2 * step) // off-by-two? 207 | + "&until=" + cubism_graphiteFormatDate(stop - 1000), function(text) { 208 | if (!text) return callback(new Error("unable to load data")); 209 | callback(null, cubism_graphiteParse(text)); 210 | }); 211 | }, expression += ""); 212 | 213 | metric.summarize = function(_) { 214 | sum = _; 215 | return metric; 216 | }; 217 | 218 | return metric; 219 | }; 220 | 221 | source.find = function(pattern, callback) { 222 | d3.json(host + "/metrics/find?format=completer" 223 | + "&query=" + encodeURIComponent(pattern), function(result) { 224 | if (!result) return callback(new Error("unable to find metrics")); 225 | callback(null, result.metrics.map(function(d) { return d.path; })); 226 | }); 227 | }; 228 | 229 | // Returns the graphite host. 230 | source.toString = function() { 231 | return host; 232 | }; 233 | 234 | return source; 235 | }; 236 | 237 | // Graphite understands seconds since UNIX epoch. 238 | function cubism_graphiteFormatDate(time) { 239 | return Math.floor(time / 1000); 240 | } 241 | 242 | // Helper method for parsing graphite's raw format. 243 | function cubism_graphiteParse(text) { 244 | var i = text.indexOf("|"), 245 | meta = text.substring(0, i), 246 | c = meta.lastIndexOf(","), 247 | b = meta.lastIndexOf(",", c - 1), 248 | a = meta.lastIndexOf(",", b - 1), 249 | start = meta.substring(a + 1, b) * 1000, 250 | step = meta.substring(c + 1) * 1000; 251 | return text 252 | .substring(i + 1) 253 | .split(",") 254 | .slice(1) // the first value is always None? 255 | .map(function(d) { return +d; }); 256 | } 257 | function cubism_metric(context) { 258 | if (!(context instanceof cubism_context)) throw new Error("invalid context"); 259 | this.context = context; 260 | } 261 | 262 | var cubism_metricPrototype = cubism_metric.prototype; 263 | 264 | cubism.metric = cubism_metric; 265 | 266 | cubism_metricPrototype.valueAt = function() { 267 | return NaN; 268 | }; 269 | 270 | cubism_metricPrototype.alias = function(name) { 271 | this.toString = function() { return name; }; 272 | return this; 273 | }; 274 | 275 | cubism_metricPrototype.extent = function() { 276 | var i = 0, 277 | n = this.context.size(), 278 | value, 279 | min = Infinity, 280 | max = -Infinity; 281 | while (++i < n) { 282 | value = this.valueAt(i); 283 | if (value < min) min = value; 284 | if (value > max) max = value; 285 | } 286 | return [min, max]; 287 | }; 288 | 289 | cubism_metricPrototype.on = function(type, listener) { 290 | return arguments.length < 2 ? null : this; 291 | }; 292 | 293 | cubism_metricPrototype.shift = function() { 294 | return this; 295 | }; 296 | 297 | cubism_metricPrototype.on = function() { 298 | return arguments.length < 2 ? null : this; 299 | }; 300 | 301 | cubism_contextPrototype.metric = function(request, name) { 302 | var context = this, 303 | metric = new cubism_metric(context), 304 | id = ".metric-" + ++cubism_id, 305 | start = -Infinity, 306 | stop, 307 | step = context.step(), 308 | size = context.size(), 309 | values = [], 310 | event = d3.dispatch("change"), 311 | listening = 0, 312 | fetching; 313 | 314 | // Prefetch new data into a temporary array. 315 | function prepare(start1, stop) { 316 | var steps = Math.min(size, Math.round((start1 - start) / step)); 317 | if (!steps || fetching) return; // already fetched, or fetching! 318 | fetching = true; 319 | steps = Math.min(size, steps + cubism_metricOverlap); 320 | var start0 = new Date(stop - steps * step); 321 | request(start0, stop, step, function(error, data) { 322 | fetching = false; 323 | if (error) return console.warn(error); 324 | var i = isFinite(start) ? Math.round((start0 - start) / step) : 0; 325 | for (var j = 0, m = data.length; j < m; ++j) values[j + i] = data[j]; 326 | event.change.call(metric, start, stop); 327 | }); 328 | } 329 | 330 | // When the context changes, switch to the new data, ready-or-not! 331 | function beforechange(start1, stop1) { 332 | if (!isFinite(start)) start = start1; 333 | values.splice(0, Math.max(0, Math.min(size, Math.round((start1 - start) / step)))); 334 | start = start1; 335 | stop = stop1; 336 | } 337 | 338 | // 339 | metric.valueAt = function(i) { 340 | return values[i]; 341 | }; 342 | 343 | // 344 | metric.shift = function(offset) { 345 | return context.metric(cubism_metricShift(request, +offset)); 346 | }; 347 | 348 | // 349 | metric.on = function(type, listener) { 350 | if (!arguments.length) return event.on(type); 351 | 352 | // If there are no listeners, then stop listening to the context, 353 | // and avoid unnecessary fetches. 354 | if (listener == null) { 355 | if (event.on(type) != null && --listening == 0) { 356 | context.on("prepare" + id, null).on("beforechange" + id, null); 357 | } 358 | } else { 359 | if (event.on(type) == null && ++listening == 1) { 360 | context.on("prepare" + id, prepare).on("beforechange" + id, beforechange); 361 | } 362 | } 363 | 364 | event.on(type, listener); 365 | 366 | // Notify the listener of the current start and stop time, as appropriate. 367 | // This way, charts can display synchronous metrics immediately. 368 | if (listener != null) { 369 | if (/^change(\.|$)/.test(type)) listener.call(context, start, stop); 370 | } 371 | 372 | return metric; 373 | }; 374 | 375 | // 376 | if (arguments.length > 1) metric.toString = function() { 377 | return name; 378 | }; 379 | 380 | return metric; 381 | }; 382 | 383 | // Number of metric to refetch each period, in case of lag. 384 | var cubism_metricOverlap = 6; 385 | 386 | // Wraps the specified request implementation, and shifts time by the given offset. 387 | function cubism_metricShift(request, offset) { 388 | return function(start, stop, step, callback) { 389 | request(new Date(+start + offset), new Date(+stop + offset), step, callback); 390 | }; 391 | } 392 | function cubism_metricConstant(context, value) { 393 | cubism_metric.call(this, context); 394 | value = +value; 395 | var name = value + ""; 396 | this.valueOf = function() { return value; }; 397 | this.toString = function() { return name; }; 398 | } 399 | 400 | var cubism_metricConstantPrototype = cubism_metricConstant.prototype = Object.create(cubism_metric.prototype); 401 | 402 | cubism_metricConstantPrototype.valueAt = function() { 403 | return +this; 404 | }; 405 | 406 | cubism_metricConstantPrototype.extent = function() { 407 | return [+this, +this]; 408 | }; 409 | function cubism_metricOperator(name, operate) { 410 | 411 | function cubism_metricOperator(left, right) { 412 | if (!(right instanceof cubism_metric)) right = new cubism_metricConstant(left.context, right); 413 | else if (left.context !== right.context) throw new Error("mismatch context"); 414 | cubism_metric.call(this, left.context); 415 | this.left = left; 416 | this.right = right; 417 | this.toString = function() { return left + " " + name + " " + right; }; 418 | } 419 | 420 | var cubism_metricOperatorPrototype = cubism_metricOperator.prototype = Object.create(cubism_metric.prototype); 421 | 422 | cubism_metricOperatorPrototype.valueAt = function(i) { 423 | return operate(this.left.valueAt(i), this.right.valueAt(i)); 424 | }; 425 | 426 | cubism_metricOperatorPrototype.shift = function(offset) { 427 | return new cubism_metricOperator(this.left.shift(offset), this.right.shift(offset)); 428 | }; 429 | 430 | cubism_metricOperatorPrototype.on = function(type, listener) { 431 | if (arguments.length < 2) return this.left.on(type); 432 | this.left.on(type, listener); 433 | this.right.on(type, listener); 434 | return this; 435 | }; 436 | 437 | return function(right) { 438 | return new cubism_metricOperator(this, right); 439 | }; 440 | } 441 | 442 | cubism_metricPrototype.add = cubism_metricOperator("+", function(left, right) { 443 | return left + right; 444 | }); 445 | 446 | cubism_metricPrototype.subtract = cubism_metricOperator("-", function(left, right) { 447 | return left - right; 448 | }); 449 | 450 | cubism_metricPrototype.multiply = cubism_metricOperator("*", function(left, right) { 451 | return left * right; 452 | }); 453 | 454 | cubism_metricPrototype.divide = cubism_metricOperator("/", function(left, right) { 455 | return left / right; 456 | }); 457 | cubism_contextPrototype.horizon = function() { 458 | var context = this, 459 | mode = "offset", 460 | buffer = document.createElement("canvas"), 461 | width = buffer.width = context.size(), 462 | height = buffer.height = 30, 463 | scale = d3.scale.linear().interpolate(d3.interpolateRound), 464 | metric = cubism_identity, 465 | extent = null, 466 | title = cubism_identity, 467 | format = d3.format(".2s"), 468 | colors = ["#08519c","#3182bd","#6baed6","#bdd7e7","#bae4b3","#74c476","#31a354","#006d2c"]; 469 | 470 | function horizon(selection) { 471 | 472 | selection 473 | .on("mousemove.horizon", function() { context.focus(d3.mouse(this)[0]); }) 474 | .on("mouseout.horizon", function() { context.focus(null); }); 475 | 476 | selection.append("canvas") 477 | .attr("width", width) 478 | .attr("height", height); 479 | 480 | selection.append("span") 481 | .attr("class", "title") 482 | .text(title); 483 | 484 | selection.append("span") 485 | .attr("class", "value"); 486 | 487 | selection.each(function(d, i) { 488 | var that = this, 489 | id = ++cubism_id, 490 | metric_ = typeof metric === "function" ? metric.call(that, d, i) : metric, 491 | colors_ = typeof colors === "function" ? colors.call(that, d, i) : colors, 492 | extent_ = typeof extent === "function" ? extent.call(that, d, i) : extent, 493 | start = -Infinity, 494 | step = context.step(), 495 | canvas = d3.select(that).select("canvas"), 496 | span = d3.select(that).select(".value"), 497 | max_, 498 | m = colors_.length >> 1, 499 | ready; 500 | 501 | canvas.datum({id: id, metric: metric_}); 502 | canvas = canvas.node().getContext("2d"); 503 | 504 | function change(start1, stop) { 505 | canvas.save(); 506 | 507 | // compute the new extent and ready flag 508 | var extent = metric_.extent(); 509 | ready = extent.every(isFinite); 510 | if (extent_ != null) extent = extent_; 511 | 512 | // if this is an update (with no extent change), copy old values! 513 | var i0 = 0, max = Math.max(-extent[0], extent[1]); 514 | if (this === context) { 515 | if (max == max_) { 516 | i0 = width - cubism_metricOverlap; 517 | var dx = (start1 - start) / step; 518 | if (dx < width) { 519 | var canvas0 = buffer.getContext("2d"); 520 | canvas0.clearRect(0, 0, width, height); 521 | canvas0.drawImage(canvas.canvas, dx, 0, width - dx, height, 0, 0, width - dx, height); 522 | canvas.clearRect(0, 0, width, height); 523 | canvas.drawImage(canvas0.canvas, 0, 0); 524 | } 525 | } 526 | start = start1; 527 | } 528 | 529 | // update the domain 530 | scale.domain([0, max_ = max]); 531 | 532 | // clear for the new data 533 | canvas.clearRect(i0, 0, width - i0, height); 534 | 535 | // record whether there are negative values to display 536 | var negative; 537 | 538 | // positive bands 539 | for (var j = 0; j < m; ++j) { 540 | canvas.fillStyle = colors_[m + j]; 541 | 542 | // Adjust the range based on the current band index. 543 | var y0 = (j - m + 1) * height; 544 | scale.range([m * height + y0, y0]); 545 | y0 = scale(0); 546 | 547 | for (var i = i0, n = width, y1; i < n; ++i) { 548 | y1 = metric_.valueAt(i); 549 | if (y1 <= 0) { negative = true; continue; } 550 | canvas.fillRect(i, y1 = scale(y1), 1, y0 - y1); 551 | } 552 | } 553 | 554 | if (negative) { 555 | // enable offset mode 556 | if (mode === "offset") { 557 | canvas.translate(0, height); 558 | canvas.scale(1, -1); 559 | } 560 | 561 | // negative bands 562 | for (var j = 0; j < m; ++j) { 563 | canvas.fillStyle = colors_[m - 1 - j]; 564 | 565 | // Adjust the range based on the current band index. 566 | var y0 = (j - m + 1) * height; 567 | scale.range([m * height + y0, y0]); 568 | y0 = scale(0); 569 | 570 | for (var i = i0, n = width, y1; i < n; ++i) { 571 | y1 = metric_.valueAt(i); 572 | if (y1 >= 0) continue; 573 | canvas.fillRect(i, scale(-y1), 1, y0 - scale(-y1)); 574 | } 575 | } 576 | } 577 | 578 | canvas.restore(); 579 | } 580 | 581 | function focus(i) { 582 | if (i == null) i = width - 1; 583 | var value = metric_.valueAt(i); 584 | span.datum(value).text(isNaN(value) ? null : format); 585 | } 586 | 587 | // Update the chart when the context changes. 588 | context.on("change.horizon-" + id, change); 589 | context.on("focus.horizon-" + id, focus); 590 | 591 | // Display the first metric change immediately, 592 | // but defer subsequent updates to the canvas change. 593 | // Note that someone still needs to listen to the metric, 594 | // so that it continues to update automatically. 595 | metric_.on("change.horizon-" + id, function(start, stop) { 596 | change(start, stop), focus(); 597 | if (ready) metric_.on("change.horizon-" + id, cubism_identity); 598 | }); 599 | }); 600 | } 601 | 602 | horizon.remove = function(selection) { 603 | 604 | selection 605 | .on("mousemove.horizon", null) 606 | .on("mouseout.horizon", null); 607 | 608 | selection.selectAll("canvas") 609 | .each(remove) 610 | .remove(); 611 | 612 | selection.selectAll(".title,.value") 613 | .remove(); 614 | 615 | function remove(d) { 616 | d.metric.on("change.horizon-" + d.id, null); 617 | context.on("change.horizon-" + d.id, null); 618 | context.on("focus.horizon-" + d.id, null); 619 | } 620 | }; 621 | 622 | horizon.mode = function(_) { 623 | if (!arguments.length) return mode; 624 | mode = _ + ""; 625 | return horizon; 626 | }; 627 | 628 | horizon.height = function(_) { 629 | if (!arguments.length) return height; 630 | buffer.height = height = +_; 631 | return horizon; 632 | }; 633 | 634 | horizon.metric = function(_) { 635 | if (!arguments.length) return metric; 636 | metric = _; 637 | return horizon; 638 | }; 639 | 640 | horizon.scale = function(_) { 641 | if (!arguments.length) return scale; 642 | scale = _; 643 | return horizon; 644 | }; 645 | 646 | horizon.extent = function(_) { 647 | if (!arguments.length) return extent; 648 | extent = _; 649 | return horizon; 650 | }; 651 | 652 | horizon.title = function(_) { 653 | if (!arguments.length) return title; 654 | title = _; 655 | return horizon; 656 | }; 657 | 658 | horizon.format = function(_) { 659 | if (!arguments.length) return format; 660 | format = _; 661 | return horizon; 662 | }; 663 | 664 | horizon.colors = function(_) { 665 | if (!arguments.length) return colors; 666 | colors = _; 667 | return horizon; 668 | }; 669 | 670 | return horizon; 671 | }; 672 | cubism_contextPrototype.comparison = function() { 673 | var context = this, 674 | width = context.size(), 675 | height = 120, 676 | scale = d3.scale.linear().interpolate(d3.interpolateRound), 677 | primary = function(d) { return d[0]; }, 678 | secondary = function(d) { return d[1]; }, 679 | extent = null, 680 | title = cubism_identity, 681 | formatPrimary = cubism_comparisonPrimaryFormat, 682 | formatChange = cubism_comparisonChangeFormat, 683 | colors = ["#9ecae1", "#225b84", "#a1d99b", "#22723a"], 684 | strokeWidth = 1.5; 685 | 686 | function comparison(selection) { 687 | 688 | selection 689 | .on("mousemove.comparison", function() { context.focus(d3.mouse(this)[0]); }) 690 | .on("mouseout.comparison", function() { context.focus(null); }); 691 | 692 | selection.append("canvas") 693 | .attr("width", width) 694 | .attr("height", height); 695 | 696 | selection.append("span") 697 | .attr("class", "title") 698 | .text(title); 699 | 700 | selection.append("span") 701 | .attr("class", "value primary"); 702 | 703 | selection.append("span") 704 | .attr("class", "value change"); 705 | 706 | selection.each(function(d, i) { 707 | var that = this, 708 | id = ++cubism_id, 709 | primary_ = typeof primary === "function" ? primary.call(that, d, i) : primary, 710 | secondary_ = typeof secondary === "function" ? secondary.call(that, d, i) : secondary, 711 | extent_ = typeof extent === "function" ? extent.call(that, d, i) : extent, 712 | div = d3.select(that), 713 | canvas = div.select("canvas"), 714 | spanPrimary = div.select(".value.primary"), 715 | spanChange = div.select(".value.change"), 716 | ready; 717 | 718 | canvas.datum({id: id, primary: primary_, secondary: secondary_}); 719 | canvas = canvas.node().getContext("2d"); 720 | 721 | function change(start, stop) { 722 | canvas.save(); 723 | canvas.clearRect(0, 0, width, height); 724 | 725 | // update the scale 726 | var primaryExtent = primary_.extent(), 727 | secondaryExtent = secondary_.extent(), 728 | extent = extent_ == null ? primaryExtent : extent_; 729 | scale.domain(extent).range([height, 0]); 730 | ready = primaryExtent.concat(secondaryExtent).every(isFinite); 731 | 732 | // consistent overplotting 733 | var round = start / context.step() & 1 734 | ? cubism_comparisonRoundOdd 735 | : cubism_comparisonRoundEven; 736 | 737 | // positive changes 738 | canvas.fillStyle = colors[2]; 739 | for (var i = 0, n = width; i < n; ++i) { 740 | var y0 = scale(primary_.valueAt(i)), 741 | y1 = scale(secondary_.valueAt(i)); 742 | if (y0 < y1) canvas.fillRect(round(i), y0, 1, y1 - y0); 743 | } 744 | 745 | // negative changes 746 | canvas.fillStyle = colors[0]; 747 | for (i = 0; i < n; ++i) { 748 | var y0 = scale(primary_.valueAt(i)), 749 | y1 = scale(secondary_.valueAt(i)); 750 | if (y0 > y1) canvas.fillRect(round(i), y1, 1, y0 - y1); 751 | } 752 | 753 | // positive values 754 | canvas.fillStyle = colors[3]; 755 | for (i = 0; i < n; ++i) { 756 | var y0 = scale(primary_.valueAt(i)), 757 | y1 = scale(secondary_.valueAt(i)); 758 | if (y0 <= y1) canvas.fillRect(round(i), y0, 1, strokeWidth); 759 | } 760 | 761 | // negative values 762 | canvas.fillStyle = colors[1]; 763 | for (i = 0; i < n; ++i) { 764 | var y0 = scale(primary_.valueAt(i)), 765 | y1 = scale(secondary_.valueAt(i)); 766 | if (y0 > y1) canvas.fillRect(round(i), y0 - strokeWidth, 1, strokeWidth); 767 | } 768 | 769 | canvas.restore(); 770 | } 771 | 772 | function focus(i) { 773 | if (i == null) i = width - 1; 774 | var valuePrimary = primary_.valueAt(i), 775 | valueSecondary = secondary_.valueAt(i), 776 | valueChange = (valuePrimary - valueSecondary) / valueSecondary; 777 | 778 | spanPrimary 779 | .datum(valuePrimary) 780 | .text(isNaN(valuePrimary) ? null : formatPrimary); 781 | 782 | spanChange 783 | .datum(valueChange) 784 | .text(isNaN(valueChange) ? null : formatChange) 785 | .attr("class", "value change " + (valueChange > 0 ? "positive" : valueChange < 0 ? "negative" : "")); 786 | } 787 | 788 | // Display the first primary change immediately, 789 | // but defer subsequent updates to the context change. 790 | // Note that someone still needs to listen to the metric, 791 | // so that it continues to update automatically. 792 | primary_.on("change.comparison-" + id, firstChange); 793 | secondary_.on("change.comparison-" + id, firstChange); 794 | function firstChange(start, stop) { 795 | change(start, stop), focus(); 796 | if (ready) { 797 | primary_.on("change.comparison-" + id, cubism_identity); 798 | secondary_.on("change.comparison-" + id, cubism_identity); 799 | } 800 | } 801 | 802 | // Update the chart when the context changes. 803 | context.on("change.comparison-" + id, change); 804 | context.on("focus.comparison-" + id, focus); 805 | }); 806 | } 807 | 808 | comparison.remove = function(selection) { 809 | 810 | selection 811 | .on("mousemove.comparison", null) 812 | .on("mouseout.comparison", null); 813 | 814 | selection.selectAll("canvas") 815 | .each(remove) 816 | .remove(); 817 | 818 | selection.selectAll(".title,.value") 819 | .remove(); 820 | 821 | function remove(d) { 822 | d.primary.on("change.comparison-" + d.id, null); 823 | d.secondary.on("change.comparison-" + d.id, null); 824 | context.on("change.comparison-" + d.id, null); 825 | context.on("focus.comparison-" + d.id, null); 826 | } 827 | }; 828 | 829 | comparison.height = function(_) { 830 | if (!arguments.length) return height; 831 | height = +_; 832 | return comparison; 833 | }; 834 | 835 | comparison.primary = function(_) { 836 | if (!arguments.length) return primary; 837 | primary = _; 838 | return comparison; 839 | }; 840 | 841 | comparison.secondary = function(_) { 842 | if (!arguments.length) return secondary; 843 | secondary = _; 844 | return comparison; 845 | }; 846 | 847 | comparison.scale = function(_) { 848 | if (!arguments.length) return scale; 849 | scale = _; 850 | return comparison; 851 | }; 852 | 853 | comparison.extent = function(_) { 854 | if (!arguments.length) return extent; 855 | extent = _; 856 | return comparison; 857 | }; 858 | 859 | comparison.title = function(_) { 860 | if (!arguments.length) return title; 861 | title = _; 862 | return comparison; 863 | }; 864 | 865 | comparison.formatPrimary = function(_) { 866 | if (!arguments.length) return formatPrimary; 867 | formatPrimary = _; 868 | return comparison; 869 | }; 870 | 871 | comparison.formatChange = function(_) { 872 | if (!arguments.length) return formatChange; 873 | formatChange = _; 874 | return comparison; 875 | }; 876 | 877 | comparison.colors = function(_) { 878 | if (!arguments.length) return colors; 879 | colors = _; 880 | return comparison; 881 | }; 882 | 883 | comparison.strokeWidth = function(_) { 884 | if (!arguments.length) return strokeWidth; 885 | strokeWidth = _; 886 | return comparison; 887 | }; 888 | 889 | return comparison; 890 | }; 891 | 892 | var cubism_comparisonPrimaryFormat = d3.format(".2s"), 893 | cubism_comparisonChangeFormat = d3.format("+.0%"); 894 | 895 | function cubism_comparisonRoundEven(i) { 896 | return i & 0xfffffe; 897 | } 898 | 899 | function cubism_comparisonRoundOdd(i) { 900 | return ((i + 1) & 0xfffffe) - 1; 901 | } 902 | cubism_contextPrototype.axis = function() { 903 | var context = this, 904 | scale = context.scale, 905 | axis_ = d3.svg.axis().scale(scale); 906 | 907 | var format = context.step() < 6e4 ? cubism_axisFormatSeconds 908 | : context.step() < 864e5 ? cubism_axisFormatMinutes 909 | : cubism_axisFormatDays; 910 | 911 | function axis(selection) { 912 | var id = ++cubism_id, 913 | tick; 914 | 915 | var g = selection.append("svg") 916 | .datum({id: id}) 917 | .attr("width", context.size()) 918 | .attr("height", Math.max(28, -axis.tickSize())) 919 | .append("g") 920 | .attr("transform", "translate(0," + (axis_.orient() === "top" ? 27 : 4) + ")") 921 | .call(axis_); 922 | 923 | context.on("change.axis-" + id, function() { 924 | g.call(axis_); 925 | if (!tick) tick = d3.select(g.node().appendChild(g.selectAll("text").node().cloneNode(true))) 926 | .style("display", "none") 927 | .text(null); 928 | }); 929 | 930 | context.on("focus.axis-" + id, function(i) { 931 | if (tick) { 932 | if (i == null) { 933 | tick.style("display", "none"); 934 | g.selectAll("text").style("fill-opacity", null); 935 | } else { 936 | tick.style("display", null).attr("x", i).text(format(scale.invert(i))); 937 | var dx = tick.node().getComputedTextLength() + 6; 938 | g.selectAll("text").style("fill-opacity", function(d) { return Math.abs(scale(d) - i) < dx ? 0 : 1; }); 939 | } 940 | } 941 | }); 942 | } 943 | 944 | axis.remove = function(selection) { 945 | 946 | selection.selectAll("svg") 947 | .each(remove) 948 | .remove(); 949 | 950 | function remove(d) { 951 | context.on("change.axis-" + d.id, null); 952 | context.on("focus.axis-" + d.id, null); 953 | } 954 | }; 955 | 956 | return d3.rebind(axis, axis_, 957 | "orient", 958 | "ticks", 959 | "tickSubdivide", 960 | "tickSize", 961 | "tickPadding", 962 | "tickFormat"); 963 | }; 964 | 965 | var cubism_axisFormatSeconds = d3.time.format("%I:%M:%S %p"), 966 | cubism_axisFormatMinutes = d3.time.format("%I:%M %p"), 967 | cubism_axisFormatDays = d3.time.format("%B %d"); 968 | cubism_contextPrototype.rule = function() { 969 | var context = this, 970 | metric = cubism_identity; 971 | 972 | function rule(selection) { 973 | var id = ++cubism_id; 974 | 975 | var line = selection.append("div") 976 | .datum({id: id}) 977 | .attr("class", "line") 978 | .call(cubism_ruleStyle); 979 | 980 | selection.each(function(d, i) { 981 | var that = this, 982 | id = ++cubism_id, 983 | metric_ = typeof metric === "function" ? metric.call(that, d, i) : metric; 984 | 985 | if (!metric_) return; 986 | 987 | function change(start, stop) { 988 | var values = []; 989 | 990 | for (var i = 0, n = context.size(); i < n; ++i) { 991 | if (metric_.valueAt(i)) { 992 | values.push(i); 993 | } 994 | } 995 | 996 | var lines = selection.selectAll(".metric").data(values); 997 | lines.exit().remove(); 998 | lines.enter().append("div").attr("class", "metric line").call(cubism_ruleStyle); 999 | lines.style("left", cubism_ruleLeft); 1000 | } 1001 | 1002 | context.on("change.rule-" + id, change); 1003 | metric_.on("change.rule-" + id, change); 1004 | }); 1005 | 1006 | context.on("focus.rule-" + id, function(i) { 1007 | line.datum(i) 1008 | .style("display", i == null ? "none" : null) 1009 | .style("left", cubism_ruleLeft); 1010 | }); 1011 | } 1012 | 1013 | rule.remove = function(selection) { 1014 | 1015 | selection.selectAll(".line") 1016 | .each(remove) 1017 | .remove(); 1018 | 1019 | function remove(d) { 1020 | context.on("focus.rule-" + d.id, null); 1021 | } 1022 | }; 1023 | 1024 | rule.metric = function(_) { 1025 | if (!arguments.length) return metric; 1026 | metric = _; 1027 | return rule; 1028 | }; 1029 | 1030 | return rule; 1031 | }; 1032 | 1033 | function cubism_ruleStyle(line) { 1034 | line 1035 | .style("position", "absolute") 1036 | .style("top", 0) 1037 | .style("bottom", 0) 1038 | .style("width", "1px") 1039 | .style("pointer-events", "none"); 1040 | } 1041 | 1042 | function cubism_ruleLeft(i) { 1043 | return i + "px"; 1044 | } 1045 | })(this); -------------------------------------------------------------------------------- /public/js/highlight.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2006, Ivan Sagalaev. 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without modification, 7 | are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, this 13 | list of conditions and the following disclaimer in the documentation and/or 14 | other materials provided with the distribution. 15 | 16 | * Neither the name of highlight.js nor the names of its contributors may be used 17 | to endorse or promote products derived from this software without specific 18 | prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS "AS IS" AND ANY 21 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 22 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY 24 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 25 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 26 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 27 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 29 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | 31 | */ 32 | var hljs=new function(){function m(p){return p.replace(/&/gm,"&").replace(/"}while(y.length||z.length){var v=u().splice(0,1)[0];w+=m(x.substr(r,v.offset-r));r=v.offset;if(v.event=="start"){w+=s(v.node);t.push(v.node)}else{if(v.event=="stop"){var q=t.length;do{q--;var p=t[q];w+=("")}while(p!=v.node);t.splice(q,1);while(q'+m(L[0])+""}else{N+=m(L[0])}P=O.lR.lastIndex;L=O.lR.exec(M)}N+=m(M.substr(P,M.length-P));return N}function K(r,M){if(M.sL&&d[M.sL]){var L=e(M.sL,r);t+=L.keyword_count;return L.value}else{return F(r,M)}}function I(M,r){var L=M.cN?'':"";if(M.rB){q+=L;M.buffer=""}else{if(M.eB){q+=m(r)+L;M.buffer=""}else{q+=L;M.buffer=r}}C.push(M);B+=M.r}function E(O,L,Q){var R=C[C.length-1];if(Q){q+=K(R.buffer+O,R);return false}var M=z(L,R);if(M){q+=K(R.buffer+O,R);I(M,L);return M.rB}var r=w(C.length-1,L);if(r){var N=R.cN?"":"";if(R.rE){q+=K(R.buffer+O,R)+N}else{if(R.eE){q+=K(R.buffer+O,R)+N+m(L)}else{q+=K(R.buffer+O+L,R)+N}}while(r>1){N=C[C.length-2].cN?"":"";q+=N;r--;C.length--}var P=C[C.length-1];C.length--;C[C.length-1].buffer="";if(P.starts){I(P.starts,"")}return R.rE}if(x(L,R)){throw"Illegal"}}var H=d[J];var C=[H.dM];var B=0;var t=0;var q="";try{var v=0;H.dM.buffer="";do{var y=s(D,v);var u=E(y[0],y[1],y[2]);v+=y[0].length;if(!u){v+=y[1].length}}while(!y[2]);if(C.length>1){throw"Illegal"}return{r:B,keyword_count:t,value:q}}catch(G){if(G=="Illegal"){return{r:0,keyword_count:0,value:m(D)}}else{throw G}}}function f(t){var r={keyword_count:0,r:0,value:m(t)};var q=r;for(var p in d){if(!d.hasOwnProperty(p)){continue}var s=e(p,t);s.language=p;if(s.keyword_count+s.r>q.keyword_count+q.r){q=s}if(s.keyword_count+s.r>r.keyword_count+r.r){q=r;r=s}}if(q.language){r.second_best=q}return r}function h(r,q,p){if(q){r=r.replace(/^((<[^>]+>|\t)+)/gm,function(t,w,v,u){return w.replace(/\t/g,q)})}if(p){r=r.replace(/\n/g,"
")}return r}function o(u,x,q){var y=g(u,q);var s=a(u);if(s=="no-highlight"){return}if(s){var w=e(s,y)}else{var w=f(y);s=w.language}var p=b(u);if(p.length){var r=document.createElement("pre");r.innerHTML=w.value;w.value=l(p,b(r),y)}w.value=h(w.value,x,q);var t=u.className;if(!t.match("(\\s|^)(language-)?"+s+"(\\s|$)")){t=t?(t+" "+s):s}if(/MSIE [678]/.test(navigator.userAgent)&&u.tagName=="CODE"&&u.parentNode.tagName=="PRE"){var r=u.parentNode;var v=document.createElement("div");v.innerHTML="
"+w.value+"
";u=v.firstChild.firstChild;v.firstChild.cN=r.cN;r.parentNode.replaceChild(v.firstChild,r)}else{u.innerHTML=w.value}u.className=t;u.result={language:s,kw:w.keyword_count,re:w.r};if(w.second_best){u.second_best={language:w.second_best.language,kw:w.second_best.keyword_count,re:w.second_best.r}}}function k(){if(k.called){return}k.called=true;var r=document.getElementsByTagName("pre");for(var p=0;p|>=|>>|>>=|>>>|>>>=|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~";this.BE={b:"\\\\.",r:0};this.ASM={cN:"string",b:"'",e:"'",i:"\\n",c:[this.BE],r:0};this.QSM={cN:"string",b:'"',e:'"',i:"\\n",c:[this.BE],r:0};this.CLCM={cN:"comment",b:"//",e:"$"};this.CBLCLM={cN:"comment",b:"/\\*",e:"\\*/"};this.HCM={cN:"comment",b:"#",e:"$"};this.NM={cN:"number",b:this.NR,r:0};this.CNM={cN:"number",b:this.CNR,r:0};this.inherit=function(p,s){var r={};for(var q in p){r[q]=p[q]}if(s){for(var q in s){r[q]=s[q]}}return r}}();hljs.LANGUAGES.javascript={dM:{k:{keyword:{"in":1,"if":1,"for":1,"while":1,"finally":1,"var":1,"new":1,"function":1,"do":1,"return":1,"void":1,"else":1,"break":1,"catch":1,"instanceof":1,"with":1,"throw":1,"case":1,"default":1,"try":1,"this":1,"switch":1,"continue":1,"typeof":1,"delete":1},literal:{"true":1,"false":1,"null":1}},c:[hljs.ASM,hljs.QSM,hljs.CLCM,hljs.CBLCLM,hljs.CNM,{b:"("+hljs.RSR+"|case|return|throw)\\s*",k:{"return":1,"throw":1,"case":1},c:[hljs.CLCM,hljs.CBLCLM,{cN:"regexp",b:"/",e:"/[gim]*",c:[{b:"\\\\/"}]}],r:0},{cN:"function",b:"\\bfunction\\b",e:"{",k:{"function":1},c:[{cN:"title",b:"[A-Za-z$_][0-9A-Za-z$_]*"},{cN:"params",b:"\\(",e:"\\)",c:[hljs.ASM,hljs.QSM,hljs.CLCM,hljs.CBLCLM]}]}]}};hljs.LANGUAGES.css=function(){var a={cN:"function",b:hljs.IR+"\\(",e:"\\)",c:[{eW:true,eE:true,c:[hljs.NM,hljs.ASM,hljs.QSM]}]};return{cI:true,dM:{i:"[=/|']",c:[hljs.CBLCLM,{cN:"id",b:"\\#[A-Za-z0-9_-]+"},{cN:"class",b:"\\.[A-Za-z0-9_-]+",r:0},{cN:"attr_selector",b:"\\[",e:"\\]",i:"$"},{cN:"pseudo",b:":(:)?[a-zA-Z0-9\\_\\-\\+\\(\\)\\\"\\']+"},{cN:"at_rule",b:"@(font-face|page)",l:"[a-z-]+",k:{"font-face":1,page:1}},{cN:"at_rule",b:"@",e:"[{;]",eE:true,k:{"import":1,page:1,media:1,charset:1},c:[a,hljs.ASM,hljs.QSM,hljs.NM]},{cN:"tag",b:hljs.IR,r:0},{cN:"rules",b:"{",e:"}",i:"[^\\s]",r:0,c:[hljs.CBLCLM,{cN:"rule",b:"[^\\s]",rB:true,e:";",eW:true,c:[{cN:"attribute",b:"[A-Z\\_\\.\\-]+",e:":",eE:true,i:"[^\\s]",starts:{cN:"value",eW:true,eE:true,c:[a,hljs.NM,hljs.QSM,hljs.ASM,hljs.CBLCLM,{cN:"hexcolor",b:"\\#[0-9A-F]+"},{cN:"important",b:"!important"}]}}]}]}]}}}();hljs.LANGUAGES.xml=function(){var b="[A-Za-z0-9\\._:-]+";var a={eW:true,c:[{cN:"attribute",b:b,r:0},{b:'="',rB:true,e:'"',c:[{cN:"value",b:'"',eW:true}]},{b:"='",rB:true,e:"'",c:[{cN:"value",b:"'",eW:true}]},{b:"=",c:[{cN:"value",b:"[^\\s/>]+"}]}]};return{cI:true,dM:{c:[{cN:"pi",b:"<\\?",e:"\\?>",r:10},{cN:"doctype",b:"",r:10,c:[{b:"\\[",e:"\\]"}]},{cN:"comment",b:"",r:10},{cN:"cdata",b:"<\\!\\[CDATA\\[",e:"\\]\\]>",r:10},{cN:"tag",b:"",k:{title:{style:1}},c:[a],starts:{cN:"css",e:"",rE:true,sL:"css"}},{cN:"tag",b:"",k:{title:{script:1}},c:[a],starts:{cN:"javascript",e:"<\/script>",rE:true,sL:"javascript"}},{cN:"vbscript",b:"<%",e:"%>",sL:"vbscript"},{cN:"tag",b:"",c:[{cN:"title",b:"[^ />]+"},a]}]}}}(); 33 | d3.selectAll("code:not([class])").classed("javascript", 1); 34 | d3.selectAll("code").each(function() { hljs.highlightBlock(this); }); -------------------------------------------------------------------------------- /public/js/index.js: -------------------------------------------------------------------------------- 1 | 2 | websocket(function(socket) { 3 | 4 | var cache = {}; 5 | var metrics = []; 6 | var keys = {}; 7 | 8 | socket.on('data', function(message) { 9 | 10 | try { message = JSON.parse(message); } catch($) {} 11 | 12 | if (!cache[message.key]) cache[message.key] = []; 13 | cache[message.key].push(message.value); 14 | 15 | if (!keys[message.key]) { 16 | metrics.push(metric(message.key)); 17 | metrics.sort(function(a, b) { return a - b }); 18 | keys[message.key] = Date.now(); 19 | render(); 20 | } 21 | }); 22 | 23 | var context = cubism.context() 24 | .serverDelay(0) 25 | .clientDelay(0) 26 | .step(1e3) 27 | .size(960); 28 | 29 | function metric(name) { 30 | 31 | cache[name] = []; 32 | 33 | var last; 34 | 35 | var m = context.metric(function(start, stop, step, callback) { 36 | 37 | start = +start, stop = +stop; 38 | if (isNaN(last)) last = start; 39 | 40 | socket.write(JSON.stringify({ key: name })); 41 | 42 | cache[name] = cache[name].slice((start - stop) / step); 43 | callback(null, cache[name]); 44 | }, name); 45 | 46 | m.name = name; 47 | return m; 48 | } 49 | 50 | function render() { 51 | d3.select("#main").call(function(div) { 52 | 53 | div 54 | .append("div") 55 | .attr("class", "axis") 56 | .call(context.axis().orient("top")); 57 | 58 | div 59 | .selectAll(".horizon") 60 | .data(metrics) 61 | .enter().append("div") 62 | .attr("class", "horizon") 63 | .call(context.horizon().extent([-20, 20]).height(125)); 64 | 65 | div.append("div") 66 | .attr("class", "rule") 67 | .call(context.rule()); 68 | 69 | }); 70 | 71 | // On mousemove, reposition the chart values to match the rule. 72 | context.on("focus", function(i) { 73 | var px = i == null ? null : context.size() - i + "px"; 74 | d3.selectAll(".value").style("right", px); 75 | }); 76 | } 77 | 78 | }); 79 | -------------------------------------------------------------------------------- /public/js/websocket.min.js: -------------------------------------------------------------------------------- 1 | (function(){var a=function(b,c){var d=a.resolve(b,c||"/"),e=a.modules[d];if(!e)throw Error("Failed to resolve module "+b+", tried "+d);var f=a.cache[d],g=f?f.exports:e();return g};a.paths=[],a.modules={},a.cache={},a.extensions=[".js",".coffee",".json"],a._core={assert:!0,events:!0,fs:!0,path:!0,vm:!0},a.resolve=function(){return function(b,c){function h(b){if(b=d.normalize(b),a.modules[b])return b;for(var c=0;a.extensions.length>c;c++){var e=a.extensions[c];if(a.modules[b+e])return b+e}}function i(b){b=b.replace(/\/+$/,"");var c=d.normalize(b+"/package.json");if(a.modules[c]){var e=a.modules[c](),f=e.browserify;if("object"==typeof f&&f.main){var g=h(d.resolve(b,f.main));if(g)return g}else if("string"==typeof f){var g=h(d.resolve(b,f));if(g)return g}else if(e.main){var g=h(d.resolve(b,e.main));if(g)return g}}return h(b+"/index")}function j(a,b){for(var c=k(b),d=0;c.length>d;d++){var e=c[d],f=h(e+"/"+a);if(f)return f;var g=i(e+"/"+a);if(g)return g}var f=h(a);return f?f:void 0}function k(a){var b;b="/"===a?[""]:d.normalize(a).split("/");for(var c=[],e=b.length-1;e>=0;e--)if("node_modules"!==b[e]){var f=b.slice(0,e+1).join("/")+"/node_modules";c.push(f)}return c}if(c||(c="/"),a._core[b])return b;var d=a.modules.path();c=d.resolve("/",c);var e=c||"/";if(b.match(/^(?:\.\.?\/|\/)/)){var f=h(d.resolve(e,b))||i(d.resolve(e,b));if(f)return f}var g=j(b,e);if(g)return g;throw Error("Cannot find module '"+b+"'")}}(),a.alias=function(b,c){var d=a.modules.path(),e=null;try{e=a.resolve(b+"/package.json","/")}catch(f){e=a.resolve(b,"/")}for(var g=d.dirname(e),h=(Object.keys||function(a){var b=[];for(var c in a)b.push(c);return b})(a.modules),i=0;h.length>i;i++){var j=h[i];if(j.slice(0,g.length+1)===g+"/"){var k=j.slice(g.length);a.modules[c+k]=a.modules[g+k]}else j===g&&(a.modules[c]=a.modules[g])}},function(){var b={},c="undefined"!=typeof window?window:{},d=!1;a.define=function(e,f){!d&&a.modules.__browserify_process&&(b=a.modules.__browserify_process(),d=!0);var g=a._core[e]?"":a.modules.path().dirname(e),h=function(b){var c=a(b,g),d=a.cache[a.resolve(b,g)];return d&&null===d.parent&&(d.parent=i),c};h.resolve=function(b){return a.resolve(b,g)},h.modules=a.modules,h.define=a.define,h.cache=a.cache;var i={id:e,filename:e,exports:{},loaded:!1,parent:null};a.modules[e]=function(){return a.cache[e]=i,f.call(i.exports,h,i,i.exports,g,e,b,c),i.loaded=!0,i.exports}}}(),a.define("path",function(a,b,c,d,e,f){function h(a,b){for(var c=[],d=0;a.length>d;d++)b(a[d],d,a)&&c.push(a[d]);return c}function i(a,b){for(var c=0,d=a.length;d>=0;d--){var e=a[d];"."==e?a.splice(d,1):".."===e?(a.splice(d,1),c++):c&&(a.splice(d,1),c--)}if(b)for(;c--;c)a.unshift("..");return a}var j=/^(.+\/(?!$)|\/)?((?:.+?)?(\.[^.]*)?)$/;c.resolve=function(){for(var a="",b=!1,c=arguments.length;c>=-1&&!b;c--){var d=c>=0?arguments[c]:f.cwd();"string"==typeof d&&d&&(a=d+"/"+a,b="/"===d.charAt(0))}return a=i(h(a.split("/"),function(a){return!!a}),!b).join("/"),(b?"/":"")+a||"."},c.normalize=function(a){var b="/"===a.charAt(0),c="/"===a.slice(-1);return a=i(h(a.split("/"),function(a){return!!a}),!b).join("/"),a||b||(a="."),a&&c&&(a+="/"),(b?"/":"")+a},c.join=function(){var a=Array.prototype.slice.call(arguments,0);return c.normalize(h(a,function(a){return a&&"string"==typeof a}).join("/"))},c.dirname=function(a){var b=j.exec(a)[1]||"",c=!1;return b?1===b.length||c&&3>=b.length&&":"===b.charAt(1)?b:b.substring(0,b.length-1):"."},c.basename=function(a,b){var c=j.exec(a)[2]||"";return b&&c.substr(-1*b.length)===b&&(c=c.substr(0,c.length-b.length)),c},c.extname=function(a){return j.exec(a)[3]||""}}),a.define("__browserify_process",function(a,b,c,d,e,f){var f=b.exports={};f.nextTick=function(){var a="undefined"!=typeof window&&window.setImmediate,b="undefined"!=typeof window&&window.postMessage&&window.addEventListener;if(a)return function(a){return window.setImmediate(a)};if(b){var c=[];return window.addEventListener("message",function(a){if(a.source===window&&"browserify-tick"===a.data&&(a.stopPropagation(),c.length>0)){var b=c.shift();b()}},!0),function(a){c.push(a),window.postMessage("browserify-tick","*")}}return function(a){setTimeout(a,0)}}(),f.title="browser",f.browser=!0,f.env={},f.argv=[],f.binding=function(b){if("evals"===b)return a("vm");throw Error("No such module. (Possibly not yet loaded)")},function(){var c,b="/";f.cwd=function(){return b},f.chdir=function(d){c||(c=a("path")),b=c.resolve(d,b)}}()}),a.define("/package.json",function(a,b){b.exports={main:"index.js"}}),a.define("/websocket.js",function(a,b){function i(a){h.apply(this,arguments),this.socket=a,this.readable=!0,this.writable=!0;var b=this,c=function(b,c){a.on?a.on(b,c):a["on"+b]=c};c("message",function(a){a&&a.srcElement&&a instanceof MessageEvent&&(a=a.data),b.emit("data",a)}),c("close",function(){b.emit("end")}),c("error",function(a){b.emit("error",a)});var d=function(){a.send("ACK"),b.emit("connection")};c("open",d),c("connection",d)}var h=a("stream").Stream;util=a("util"),util.inherits(i,h),i.prototype.write=function(a){try{this.socket.write?this.socket.write(a):this.socket.send(a)}catch(b){this.emit("error",b)}},i.prototype.end=function(){this.emit("end")},b.exports=i}),a.define("stream",function(a,b){function j(){h.EventEmitter.call(this)}var h=a("events"),i=a("util");i.inherits(j,h.EventEmitter),b.exports=j,j.Stream=j,j.prototype.pipe=function(a,b){function d(b){a.writable&&!1===a.write(b)&&c.pause&&c.pause()}function e(){c.readable&&c.resume&&c.resume()}function g(){f||(f=!0,a._pipeCount--,j(),a._pipeCount>0||a.end())}function h(){f||(f=!0,a._pipeCount--,j(),a._pipeCount>0||a.destroy())}function i(a){if(j(),0===this.listeners("error").length)throw a}function j(){c.removeListener("data",d),a.removeListener("drain",e),c.removeListener("end",g),c.removeListener("close",h),c.removeListener("error",i),a.removeListener("error",i),c.removeListener("end",j),c.removeListener("close",j),a.removeListener("end",j),a.removeListener("close",j)}var c=this;c.on("data",d),a.on("drain",e),a._isStdio||b&&b.end===!1||(a._pipeCount=a._pipeCount||0,a._pipeCount++,c.on("end",g),c.on("close",h));var f=!1;return c.on("error",i),a.on("error",i),c.on("end",j),c.on("close",j),a.on("end",j),a.on("close",j),a.emit("pipe",c),a}}),a.define("events",function(a,b,c,d,e,f){function j(a,b){if(a.indexOf)return a.indexOf(b);for(var c=0;a.length>c;c++)if(b===a[c])return c;return-1}f.EventEmitter||(f.EventEmitter=function(){});var h=c.EventEmitter=f.EventEmitter,i="function"==typeof Array.isArray?Array.isArray:function(a){return"[object Array]"===Object.prototype.toString.call(a)},k=10;h.prototype.setMaxListeners=function(a){this._events||(this._events={}),this._events.maxListeners=a},h.prototype.emit=function(a){if("error"===a&&(!this._events||!this._events.error||i(this._events.error)&&!this._events.error.length))throw arguments[1]instanceof Error?arguments[1]:Error("Uncaught, unspecified 'error' event.");if(!this._events)return!1;var b=this._events[a];if(!b)return!1;if("function"==typeof b){switch(arguments.length){case 1:b.call(this);break;case 2:b.call(this,arguments[1]);break;case 3:b.call(this,arguments[1],arguments[2]);break;default:var c=Array.prototype.slice.call(arguments,1);b.apply(this,c)}return!0}if(i(b)){for(var c=Array.prototype.slice.call(arguments,1),d=b.slice(),e=0,f=d.length;f>e;e++)d[e].apply(this,c);return!0}return!1},h.prototype.addListener=function(a,b){if("function"!=typeof b)throw Error("addListener only takes instances of Function");if(this._events||(this._events={}),this.emit("newListener",a,b),this._events[a])if(i(this._events[a])){if(!this._events[a].warned){var c;c=void 0!==this._events.maxListeners?this._events.maxListeners:k,c&&c>0&&this._events[a].length>c&&(this._events[a].warned=!0,console.error("(node) warning: possible EventEmitter memory leak detected. %d listeners added. Use emitter.setMaxListeners() to increase limit.",this._events[a].length),console.trace())}this._events[a].push(b)}else this._events[a]=[this._events[a],b];else this._events[a]=b;return this},h.prototype.on=h.prototype.addListener,h.prototype.once=function(a,b){var c=this;return c.on(a,function d(){c.removeListener(a,d),b.apply(this,arguments)}),this},h.prototype.removeListener=function(a,b){if("function"!=typeof b)throw Error("removeListener only takes instances of Function");if(!this._events||!this._events[a])return this;var c=this._events[a];if(i(c)){var d=j(c,b);if(0>d)return this;c.splice(d,1),0==c.length&&delete this._events[a]}else this._events[a]===b&&delete this._events[a];return this},h.prototype.removeAllListeners=function(a){return a&&this._events&&this._events[a]&&(this._events[a]=null),this},h.prototype.listeners=function(a){return this._events||(this._events={}),this._events[a]||(this._events[a]=[]),i(this._events[a])||(this._events[a]=[this._events[a]]),this._events[a]}}),a.define("util",function(a,b,c){function i(a){return a instanceof Array||Array.isArray(a)||a&&a!==Object.prototype&&i(a.__proto__)}function j(a){return a instanceof RegExp||"object"==typeof a&&"[object RegExp]"===Object.prototype.toString.call(a)}function k(a){if(a instanceof Date)return!0;if("object"!=typeof a)return!1;var b=Date.prototype&&p(Date.prototype),c=a.__proto__&&p(a.__proto__);return JSON.stringify(c)===JSON.stringify(b)}a("events"),c.isArray=i,c.isDate=function(a){return"[object Date]"===Object.prototype.toString.call(a)},c.isRegExp=function(a){return"[object RegExp]"===Object.prototype.toString.call(a)},c.print=function(){},c.puts=function(){},c.debug=function(){},c.inspect=function(a,b,d,e){function h(a,d){if(a&&"function"==typeof a.inspect&&a!==c&&(!a.constructor||a.constructor.prototype!==a))return a.inspect(d);switch(typeof a){case"undefined":return g("undefined","undefined");case"string":var e="'"+JSON.stringify(a).replace(/^"|"$/g,"").replace(/'/g,"\\'").replace(/\\"/g,'"')+"'";return g(e,"string");case"number":return g(""+a,"number");case"boolean":return g(""+a,"boolean")}if(null===a)return g("null","null");var l=o(a),m=b?p(a):l;if("function"==typeof a&&0===m.length){if(j(a))return g(""+a,"regexp");var n=a.name?": "+a.name:"";return g("[Function"+n+"]","special")}if(k(a)&&0===m.length)return g(a.toUTCString(),"date");var q,r,s;if(i(a)?(r="Array",s=["[","]"]):(r="Object",s=["{","}"]),"function"==typeof a){var t=a.name?": "+a.name:"";q=j(a)?" "+a:" [Function"+t+"]"}else q="";if(k(a)&&(q=" "+a.toUTCString()),0===m.length)return s[0]+q+s[1];if(0>d)return j(a)?g(""+a,"regexp"):g("[Object]","special");f.push(a);var u=m.map(function(b){var c,e;if(a.__lookupGetter__&&(a.__lookupGetter__(b)?e=a.__lookupSetter__(b)?g("[Getter/Setter]","special"):g("[Getter]","special"):a.__lookupSetter__(b)&&(e=g("[Setter]","special"))),0>l.indexOf(b)&&(c="["+b+"]"),e||(0>f.indexOf(a[b])?(e=null===d?h(a[b]):h(a[b],d-1),e.indexOf("\n")>-1&&(e=i(a)?e.split("\n").map(function(a){return" "+a}).join("\n").substr(2):"\n"+e.split("\n").map(function(a){return" "+a}).join("\n"))):e=g("[Circular]","special")),c===void 0){if("Array"===r&&b.match(/^\d+$/))return e;c=JSON.stringify(""+b),c.match(/^"([a-zA-Z_][a-zA-Z_0-9]*)"$/)?(c=c.substr(1,c.length-2),c=g(c,"name")):(c=c.replace(/'/g,"\\'").replace(/\\"/g,'"').replace(/(^"|"$)/g,"'"),c=g(c,"string"))}return c+": "+e});f.pop();var v=0,w=u.reduce(function(a,b){return v++,b.indexOf("\n")>=0&&v++,a+b.length+1},0);return u=w>50?s[0]+(""===q?"":q+"\n ")+" "+u.join(",\n ")+" "+s[1]:s[0]+q+" "+u.join(", ")+" "+s[1]}var f=[],g=function(a,b){var c={bold:[1,22],italic:[3,23],underline:[4,24],inverse:[7,27],white:[37,39],grey:[90,39],black:[30,39],blue:[34,39],cyan:[36,39],green:[32,39],magenta:[35,39],red:[31,39],yellow:[33,39]},d={special:"cyan",number:"blue","boolean":"yellow",undefined:"grey","null":"bold",string:"green",date:"magenta",regexp:"red"}[b];return d?"["+c[d][0]+"m"+a+"["+c[d][1]+"m":a};return e||(g=function(a){return a}),h(a,d===void 0?2:d)},c.log=function(){},c.pump=null;var o=Object.keys||function(a){var b=[];for(var c in a)b.push(c);return b},p=Object.getOwnPropertyNames||function(a){var b=[];for(var c in a)Object.hasOwnProperty.call(a,c)&&b.push(c);return b},q=Object.create||function(a,b){var c;if(null===a)c={__proto__:null};else{if("object"!=typeof a)throw new TypeError("typeof prototype["+typeof a+"] != 'object'");var d=function(){};d.prototype=a,c=new d,c.__proto__=a}return b!==void 0&&Object.defineProperties&&Object.defineProperties(c,b),c};c.inherits=function(a,b){a.super_=b,a.prototype=q(b.prototype,{constructor:{value:a,enumerable:!1,writable:!0,configurable:!0}})};var r=/%[sdj%]/g;c.format=function(a){if("string"!=typeof a){for(var b=[],d=0;arguments.length>d;d++)b.push(c.inspect(arguments[d]));return b.join(" ")}for(var d=1,e=arguments,f=e.length,g=(a+"").replace(r,function(a){if("%%"===a)return"%";if(d>=f)return a;switch(a){case"%s":return e[d++]+"";case"%d":return Number(e[d++]);case"%j":return JSON.stringify(e[d++]);default:return a}}),h=e[d];f>d;h=e[++d])g+=null===h||"object"!=typeof h?" "+h:" "+c.inspect(h);return g}}),a.define("/client.js",function(a,b){var h=a("./websocket");b.exports=function(a){var b="";b="https"===window.location.protocol?"wss":"ws",b+="://"+window.location.host;var c=new WebSocket(b),d=new h(c);d.on("connection",function(){a&&a(d)})},"undefined"!=typeof window&&(window.websocket=b.exports)}),a("/client.js")})(); -------------------------------------------------------------------------------- /public/webfonts/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heapwolf/tsd/bcb185745ddc263337f4ff125caa9599e4406323/public/webfonts/.DS_Store -------------------------------------------------------------------------------- /public/webfonts/ProximaNova-RegItalic.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heapwolf/tsd/bcb185745ddc263337f4ff125caa9599e4406323/public/webfonts/ProximaNova-RegItalic.otf -------------------------------------------------------------------------------- /public/webfonts/ProximaNova-Regular.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heapwolf/tsd/bcb185745ddc263337f4ff125caa9599e4406323/public/webfonts/ProximaNova-Regular.otf -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heapwolf/tsd/bcb185745ddc263337f4ff125caa9599e4406323/screenshot.png -------------------------------------------------------------------------------- /test/server.js: -------------------------------------------------------------------------------- 1 | 2 | var net = require('net'); 3 | 4 | var x = 0; 5 | 6 | var client = net.connect({ port: 9099 }, function() { 7 | 8 | function write(json) { 9 | client.write(JSON.stringify(json) + '\n'); 10 | } 11 | 12 | setInterval(function() { 13 | 14 | x++; 15 | 16 | write({ key: 'hello', value: (Math.random() + x) / 50 }); 17 | write({ key: 'goodbye', value: (Math.random() + x) / 20 }); 18 | write({ key: 'ohai', value: 1000 }); 19 | write({ key: 'neat-stuff', value: (Math.random() + x) / 10 }); 20 | 21 | }, 150); 22 | 23 | }); 24 | --------------------------------------------------------------------------------