cubism-rails 1.2.2

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source :rubygems
2
+
3
+ # Specify your gem's dependencies in gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Vlad Gorodetsky
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,25 @@
1
+ # cubism-rails
2
+
3
+ Cubism.js is a [D3](http://mbostock.github.com/d3/) plugin for visualizing time series. Use Cubism to construct better realtime dashboards, pulling data from [Graphite](/square/cubism/wiki/Graphite), [Cube](/square/cubism/wiki/Cube) and other sources. Cubism is available under the [Apache License](/square/cubism/blob/master/LICENSE).
4
+
5
+ ## Installation
6
+
7
+ Add the following to your gemfile:
8
+
9
+ gem 'cubism-rails'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Add the following directive to your JavaScript manifest file (application.js):
16
+
17
+ //= require cubism
18
+
19
+ ## Contributing
20
+
21
+ 1. Fork it
22
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
23
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
24
+ 4. Push to the branch (`git push origin my-new-feature`)
25
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,21 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'cubism-rails/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "cubism-rails"
8
+ gem.version = Cubism::Rails::VERSION
9
+ gem.authors = ["Vlad Gorodetsky"]
10
+ gem.email = ["v@gor.io"]
11
+ gem.description = %q{Cubism.js: A JavaScript library for time series visualization.}
12
+ gem.summary = %q{Gemified cubism.js asset for Rails}
13
+ gem.homepage = "http://github.com/bai/cubism-rails"
14
+
15
+ gem.files = `git ls-files`.split($/)
16
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
17
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
+ gem.require_paths = ["lib"]
19
+
20
+ gem.add_dependency "railties", ">= 3.0", "< 5.0"
21
+ end
@@ -0,0 +1,5 @@
1
+ module Cubism
2
+ module Rails
3
+ VERSION = "1.2.2"
4
+ end
5
+ end
@@ -0,0 +1,8 @@
1
+ require "cubism-rails/version"
2
+
3
+ module Cubism
4
+ module Rails
5
+ class Engine < ::Rails::Engine
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,1045 @@
1
+ (function(exports){
2
+ var cubism = exports.cubism = {version: "1.2.2"};
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 / 1e3 + "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(Math.round(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(Math.round(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", i == null ? null : 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);
@@ -0,0 +1 @@
1
+ (function(a){function d(a){return a}function e(){}function h(a){return Math.floor(a/1e3)}function i(a){var b=a.indexOf("|"),c=a.substring(0,b),d=c.lastIndexOf(","),e=c.lastIndexOf(",",d-1),f=c.lastIndexOf(",",e-1),g=c.substring(f+1,e)*1e3,h=c.substring(d+1)*1e3;return a.substring(b+1).split(",").slice(1).map(function(a){return+a})}function j(a){if(!(a instanceof e))throw new Error("invalid context");this.context=a}function m(a,b){return function(c,d,e,f){a(new Date(+c+b),new Date(+d+b),e,f)}}function n(a,b){j.call(this,a),b=+b;var c=b+"";this.valueOf=function(){return b},this.toString=function(){return c}}function p(a,b){function c(b,c){if(c instanceof j){if(b.context!==c.context)throw new Error("mismatch context")}else c=new n(b.context,c);j.call(this,b.context),this.left=b,this.right=c,this.toString=function(){return b+" "+a+" "+c}}var d=c.prototype=Object.create(j.prototype);return d.valueAt=function(a){return b(this.left.valueAt(a),this.right.valueAt(a))},d.shift=function(a){return new c(this.left.shift(a),this.right.shift(a))},d.on=function(a,b){return arguments.length<2?this.left.on(a):(this.left.on(a,b),this.right.on(a,b),this)},function(a){return new c(this,a)}}function s(a){return a&16777214}function t(a){return(a+1&16777214)-1}function x(a){a.style("position","absolute").style("top",0).style("bottom",0).style("width","1px").style("pointer-events","none")}function y(a){return a+"px"}var b=a.cubism={version:"1.2.2"},c=0;b.option=function(a,c){var d=b.options(a);return d.length?d[0]:c},b.options=function(a,b){var c=location.search.substring(1).split("&"),d=[],e=-1,f=c.length,g;while(++e<f)(g=c[e].split("="))[0]==a&&d.push(decodeURIComponent(g[1]));return d.length||arguments.length<2?d:b},b.context=function(){function p(){var c=Date.now();return g=new Date(Math.floor((c-j-k)/b)*b),f=new Date(g-d*b),i=new Date(Math.floor((c-j)/b)*b),h=new Date(i-d*b),m.domain([f,g]),a}var a=new e,b=1e4,d=1440,f,g,h,i,j=5e3,k=5e3,l=d3.dispatch("prepare","beforechange","change","focus"),m=a.scale=d3.time.scale().range([0,d]),n,o;return a.start=function(){n&&clearTimeout(n);var c=+i+j-Date.now();return c<k&&(c+=b),n=setTimeout(function e(){i=new Date(Math.floor((Date.now()-j)/b)*b),h=new Date(i-d*b),l.prepare.call(a,h,i),setTimeout(function(){m.domain([f=h,g=i]),l.beforechange.call(a,h,i),l.change.call(a,h,i),l.focus.call(a,o)},k),n=setTimeout(e,b)},c),a},a.stop=function(){return n=clearTimeout(n),a},n=setTimeout(a.start,10),a.step=function(a){return arguments.length?(b=+a,p()):b},a.size=function(a){return arguments.length?(m.range([0,d=+a]),p()):d},a.serverDelay=function(a){return arguments.length?(j=+a,p()):j},a.clientDelay=function(a){return arguments.length?(k=+a,p()):k},a.focus=function(b){return l.focus.call(a,o=b),a},a.on=function(b,c){return arguments.length<2?l.on(b):(l.on(b,c),c!=null&&(/^prepare(\.|$)/.test(b)&&c.call(a,h,i),/^beforechange(\.|$)/.test(b)&&c.call(a,f,g),/^change(\.|$)/.test(b)&&c.call(a,f,g),/^focus(\.|$)/.test(b)&&c.call(a,o)),a)},d3.select(window).on("keydown.context-"+ ++c,function(){switch(!d3.event.metaKey&&d3.event.keyCode){case 37:o==null&&(o=d-1),o>0&&a.focus(--o);break;case 39:o==null&&(o=d-2),o<d-1&&a.focus(++o);break;default:return}d3.event.preventDefault()}),p()};var f=b.context.prototype=e.prototype;f.constant=function(a){return new n(this,+a)},f.cube=function(a){arguments.length||(a="");var b={},c=this;return b.metric=function(b){return c.metric(function(c,d,e,f){d3.json(a+"/1.0/metric"+"?expression="+encodeURIComponent(b)+"&start="+g(c)+"&stop="+g(d)+"&step="+e,function(a){if(!a)return f(new Error("unable to load data"));f(null,a.map(function(a){return a.value}))})},b+="")},b.toString=function(){return a},b};var g=d3.time.format.iso;f.graphite=function(a){arguments.length||(a="");var b={},c=this;return b.metric=function(b){var d="sum",e=c.metric(function(c,e,f,g){var j=b;f!==1e4&&(j="summarize("+j+",'"+(f%36e5?f%6e4?f/1e3+"sec":f/6e4+"min":f/36e5+"hour")+"','"+d+"')"),d3.text(a+"/render?format=raw"+"&target="+encodeURIComponent("alias("+j+",'')")+"&from="+h(c-2*f)+"&until="+h(e-1e3),function(a){if(!a)return g(new Error("unable to load data"));g(null,i(a))})},b+="");return e.summarize=function(a){return d=a,e},e},b.find=function(b,c){d3.json(a+"/metrics/find?format=completer"+"&query="+encodeURIComponent(b),function(a){if(!a)return c(new Error("unable to find metrics"));c(null,a.metrics.map(function(a){return a.path}))})},b.toString=function(){return a},b};var k=j.prototype;b.metric=j,k.valueAt=function(){return NaN},k.alias=function(a){return this.toString=function(){return a},this},k.extent=function(){var a=0,b=this.context.size(),c,d=Infinity,e=-Infinity;while(++a<b)c=this.valueAt(a),c<d&&(d=c),c>e&&(e=c);return[d,e]},k.on=function(a,b){return arguments.length<2?null:this},k.shift=function(){return this},k.on=function(){return arguments.length<2?null:this},f.metric=function(a,b){function r(b,c){var d=Math.min(k,Math.round((b-g)/i));if(!d||q)return;q=!0,d=Math.min(k,d+l);var f=new Date(c-d*i);a(f,c,i,function(a,b){q=!1;if(a)return console.warn(a);var d=isFinite(g)?Math.round((f-g)/i):0;for(var h=0,j=b.length;h<j;++h)n[h+d]=b[h];o.change.call(e,g,c)})}function s(a,b){isFinite(g)||(g=a),n.splice(0,Math.max(0,Math.min(k,Math.round((a-g)/i)))),g=a,h=b}var d=this,e=new j(d),f=".metric-"+ ++c,g=-Infinity,h,i=d.step(),k=d.size(),n=[],o=d3.dispatch("change"),p=0,q;return e.valueAt=function(a){return n[a]},e.shift=function(b){return d.metric(m(a,+b))},e.on=function(a,b){return arguments.length?(b==null?o.on(a)!=null&&--p==0&&d.on("prepare"+f,null).on("beforechange"+f,null):o.on(a)==null&&++p==1&&d.on("prepare"+f,r).on("beforechange"+f,s),o.on(a,b),b!=null&&/^change(\.|$)/.test(a)&&b.call(d,g,h),e):o.on(a)},arguments.length>1&&(e.toString=function(){return b}),e};var l=6,o=n.prototype=Object.create(j.prototype);o.valueAt=function(){return+this},o.extent=function(){return[+this,+this]},k.add=p("+",function(a,b){return a+b}),k.subtract=p("-",function(a,b){return a-b}),k.multiply=p("*",function(a,b){return a*b}),k.divide=p("/",function(a,b){return a/b}),f.horizon=function(){function o(o){o.on("mousemove.horizon",function(){a.focus(Math.round(d3.mouse(this)[0]))}).on("mouseout.horizon",function(){a.focus(null)}),o.append("canvas").attr("width",f).attr("height",g),o.append("span").attr("class","title").text(k),o.append("span").attr("class","value"),o.each(function(k,o){function B(c,d){w.save();var i=r.extent();A=i.every(isFinite),t!=null&&(i=t);var j=0,k=Math.max(-i[0],i[1]);if(this===a){if(k==y){j=f-l;var m=(c-u)/v;if(m<f){var n=e.getContext("2d");n.clearRect(0,0,f,g),n.drawImage(w.canvas,m,0,f-m,g,0,0,f-m,g),w.clearRect(0,0,f,g),w.drawImage(n.canvas,0,0)}}u=c}h.domain([0,y=k]),w.clearRect(j,0,f-j,g);var o;for(var p=0;p<z;++p){w.fillStyle=s[z+p];var q=(p-z+1)*g;h.range([z*g+q,q]),q=h(0);for(var x=j,B=f,C;x<B;++x){C=r.valueAt(x);if(C<=0){o=!0;continue}w.fillRect(x,C=h(C),1,q-C)}}if(o){b==="offset"&&(w.translate(0,g),w.scale(1,-1));for(var p=0;p<z;++p){w.fillStyle=s[z-1-p];var q=(p-z+1)*g;h.range([z*g+q,q]),q=h(0);for(var x=j,B=f,C;x<B;++x){C=r.valueAt(x);if(C>=0)continue;w.fillRect(x,h(-C),1,q-h(-C))}}}w.restore()}function C(a){a==null&&(a=f-1);var b=r.valueAt(a);x.datum(b).text(isNaN(b)?null:m)}var p=this,q=++c,r=typeof i=="function"?i.call(p,k,o):i,s=typeof n=="function"?n.call(p,k,o):n,t=typeof j=="function"?j.call(p,k,o):j,u=-Infinity,v=a.step(),w=d3.select(p).select("canvas"),x=d3.select(p).select(".value"),y,z=s.length>>1,A;w.datum({id:q,metric:r}),w=w.node().getContext("2d"),a.on("change.horizon-"+q,B),a.on("focus.horizon-"+q,C),r.on("change.horizon-"+q,function(a,b){B(a,b),C(),A&&r.on("change.horizon-"+q,d)})})}var a=this,b="offset",e=document.createElement("canvas"),f=e.width=a.size(),g=e.height=30,h=d3.scale.linear().interpolate(d3.interpolateRound),i=d,j=null,k=d,m=d3.format(".2s"),n=["#08519c","#3182bd","#6baed6","#bdd7e7","#bae4b3","#74c476","#31a354","#006d2c"];return o.remove=function(b){function c(b){b.metric.on("change.horizon-"+b.id,null),a.on("change.horizon-"+b.id,null),a.on("focus.horizon-"+b.id,null)}b.on("mousemove.horizon",null).on("mouseout.horizon",null),b.selectAll("canvas").each(c).remove(),b.selectAll(".title,.value").remove()},o.mode=function(a){return arguments.length?(b=a+"",o):b},o.height=function(a){return arguments.length?(e.height=g=+a,o):g},o.metric=function(a){return arguments.length?(i=a,o):i},o.scale=function(a){return arguments.length?(h=a,o):h},o.extent=function(a){return arguments.length?(j=a,o):j},o.title=function(a){return arguments.length?(k=a,o):k},o.format=function(a){return arguments.length?(m=a,o):m},o.colors=function(a){return arguments.length?(n=a,o):n},o},f.comparison=function(){function o(o){o.on("mousemove.comparison",function(){a.focus(Math.round(d3.mouse(this)[0]))}).on("mouseout.comparison",function(){a.focus(null)}),o.append("canvas").attr("width",b).attr("height",e),o.append("span").attr("class","title").text(j),o.append("span").attr("class","value primary"),o.append("span").attr("class","value change"),o.each(function(j,o){function B(c,d){x.save(),x.clearRect(0,0,b,e);var g=r.extent(),h=u.extent(),i=v==null?g:v;f.domain(i).range([e,0]),A=g.concat(h).every(isFinite);var j=c/a.step()&1?t:s;x.fillStyle=m[2];for(var k=0,l=b;k<l;++k){var o=f(r.valueAt(k)),p=f(u.valueAt(k));o<p&&x.fillRect(j(k),o,1,p-o)}x.fillStyle=m[0];for(k=0;k<l;++k){var o=f(r.valueAt(k)),p=f(u.valueAt(k));o>p&&x.fillRect(j(k),p,1,o-p)}x.fillStyle=m[3];for(k=0;k<l;++k){var o=f(r.valueAt(k)),p=f(u.valueAt(k));o<=p&&x.fillRect(j(k),o,1,n)}x.fillStyle=m[1];for(k=0;k<l;++k){var o=f(r.valueAt(k)),p=f(u.valueAt(k));o>p&&x.fillRect(j(k),o-n,1,n)}x.restore()}function C(a){a==null&&(a=b-1);var c=r.valueAt(a),d=u.valueAt(a),e=(c-d)/d;y.datum(c).text(isNaN(c)?null:k),z.datum(e).text(isNaN(e)?null:l).attr("class","value change "+(e>0?"positive":e<0?"negative":""))}function D(a,b){B(a,b),C(),A&&(r.on("change.comparison-"+q,d),u.on("change.comparison-"+q,d))}var p=this,q=++c,r=typeof g=="function"?g.call(p,j,o):g,u=typeof h=="function"?h.call(p,j,o):h,v=typeof i=="function"?i.call(p,j,o):i,w=d3.select(p),x=w.select("canvas"),y=w.select(".value.primary"),z=w.select(".value.change"),A;x.datum({id:q,primary:r,secondary:u}),x=x.node().getContext("2d"),r.on("change.comparison-"+q,D),u.on("change.comparison-"+q,D),a.on("change.comparison-"+q,B),a.on("focus.comparison-"+q,C)})}var a=this,b=a.size(),e=120,f=d3.scale.linear().interpolate(d3.interpolateRound),g=function(a){return a[0]},h=function(a){return a[1]},i=null,j=d,k=q,l=r,m=["#9ecae1","#225b84","#a1d99b","#22723a"],n=1.5;return o.remove=function(b){function c(b){b.primary.on("change.comparison-"+b.id,null),b.secondary.on("change.comparison-"+b.id,null),a.on("change.comparison-"+b.id,null),a.on("focus.comparison-"+b.id,null)}b.on("mousemove.comparison",null).on("mouseout.comparison",null),b.selectAll("canvas").each(c).remove(),b.selectAll(".title,.value").remove()},o.height=function(a){return arguments.length?(e=+a,o):e},o.primary=function(a){return arguments.length?(g=a,o):g},o.secondary=function(a){return arguments.length?(h=a,o):h},o.scale=function(a){return arguments.length?(f=a,o):f},o.extent=function(a){return arguments.length?(i=a,o):i},o.title=function(a){return arguments.length?(j=a,o):j},o.formatPrimary=function(a){return arguments.length?(k=a,o):k},o.formatChange=function(a){return arguments.length?(l=a,o):l},o.colors=function(a){return arguments.length?(m=a,o):m},o.strokeWidth=function(a){return arguments.length?(n=a,o):n},o};var q=d3.format(".2s"),r=d3.format("+.0%");f.axis=function(){function f(g){var h=++c,i,j=g.append("svg").datum({id:h}).attr("width",a.size()).attr("height",Math.max(28,-f.tickSize())).append("g").attr("transform","translate(0,"+(d.orient()==="top"?27:4)+")").call(d);a.on("change.axis-"+h,function(){j.call(d),i||(i=d3.select(j.node().appendChild(j.selectAll("text").node().cloneNode(!0))).style("display","none").text(null))}),a.on("focus.axis-"+h,function(a){if(i)if(a==null)i.style("display","none"),j.selectAll("text").style("fill-opacity",null);else{i.style("display",null).attr("x",a).text(e(b.invert(a)));var c=i.node().getComputedTextLength()+6;j.selectAll("text").style("fill-opacity",function(d){return Math.abs(b(d)-a)<c?0:1})}})}var a=this,b=a.scale,d=d3.svg.axis().scale(b),e=a.step()<6e4?u:a.step()<864e5?v:w;return f.remove=function(b){function c(b){a.on("change.axis-"+b.id,null),a.on("focus.axis-"+b.id,null)}b.selectAll("svg").each(c).remove()},d3.rebind(f,d,"orient","ticks","tickSubdivide","tickSize","tickPadding","tickFormat")};var u=d3.time.format("%I:%M:%S %p"),v=d3.time.format("%I:%M %p"),w=d3.time.format("%B %d");f.rule=function(){function e(d){var e=++c,f=d.append("div").datum({id:e}).attr("class","line").call(x);d.each(function(e,f){function j(b,c){var e=[];for(var f=0,g=a.size();f<g;++f)i.valueAt(f)&&e.push(f);var h=d.selectAll(".metric").data(e);h.exit().remove(),h.enter().append("div").attr("class","metric line").call(x),h.style("left",y)}var g=this,h=++c,i=typeof b=="function"?b.call(g,e,f):b;if(!i)return;a.on("change.rule-"+h,j),i.on("change.rule-"+h,j)}),a.on("focus.rule-"+e,function(a){f.datum(a).style("display",a==null?"none":null).style("left",a==null?null:y)})}var a=this,b=d;return e.remove=function(b){function c(b){a.on("focus.rule-"+b.id,null)}b.selectAll(".line").each(c).remove()},e.metric=function(a){return arguments.length?(b=a,e):b},e}})(this);
metadata ADDED
@@ -0,0 +1,77 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cubism-rails
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.2.2
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Vlad Gorodetsky
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-01-06 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: railties
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '3.0'
22
+ - - <
23
+ - !ruby/object:Gem::Version
24
+ version: '5.0'
25
+ type: :runtime
26
+ prerelease: false
27
+ version_requirements: !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '3.0'
33
+ - - <
34
+ - !ruby/object:Gem::Version
35
+ version: '5.0'
36
+ description: ! 'Cubism.js: A JavaScript library for time series visualization.'
37
+ email:
38
+ - v@gor.io
39
+ executables: []
40
+ extensions: []
41
+ extra_rdoc_files: []
42
+ files:
43
+ - .gitignore
44
+ - Gemfile
45
+ - LICENSE
46
+ - README.md
47
+ - Rakefile
48
+ - cubism-rails.gemspec
49
+ - lib/cubism-rails.rb
50
+ - lib/cubism-rails/version.rb
51
+ - vendor/assets/javascripts/cubism.js
52
+ - vendor/assets/javascripts/cubism.min.js
53
+ homepage: http://github.com/bai/cubism-rails
54
+ licenses: []
55
+ post_install_message:
56
+ rdoc_options: []
57
+ require_paths:
58
+ - lib
59
+ required_ruby_version: !ruby/object:Gem::Requirement
60
+ none: false
61
+ requirements:
62
+ - - ! '>='
63
+ - !ruby/object:Gem::Version
64
+ version: '0'
65
+ required_rubygems_version: !ruby/object:Gem::Requirement
66
+ none: false
67
+ requirements:
68
+ - - ! '>='
69
+ - !ruby/object:Gem::Version
70
+ version: '0'
71
+ requirements: []
72
+ rubyforge_project:
73
+ rubygems_version: 1.8.24
74
+ signing_key:
75
+ specification_version: 3
76
+ summary: Gemified cubism.js asset for Rails
77
+ test_files: []