├── .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 | 
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+=(""+p.nodeName.toLowerCase()+">")}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:"",rE:true,sL:"css"}},{cN:"tag",b:"