Skip to content
This repository
Browse code

Add default dashboards.

  • Loading branch information...
commit 59c80fa733b54941d66cde07286f55ecc0848136 1 parent 8009bef
Mike Bostock mbostock authored
86 static/collectd/index.html
... ... @@ -0,0 +1,86 @@
  1 +<!DOCTYPE html>
  2 +<meta charset="utf-8">
  3 +<title>Collectd</title>
  4 +<style>
  5 +
  6 +@import url(../style.css);
  7 +
  8 +</style>
  9 +<select id="step">
  10 + <option value="1e4">10 seconds</option>
  11 + <option value="6e4">1 minute</option>
  12 + <option value="3e5">5 minutes</option>
  13 +</select>
  14 +<script src="../d3.v2.js"></script>
  15 +<script src="../cubism.v1.js"></script>
  16 +<script>
  17 +
  18 +//
  19 +var step = +cubism.option("step", 1e4);
  20 +
  21 +//
  22 +var context = cubism.context()
  23 + .serverDelay(1e4)
  24 + .step(step)
  25 + .size(1440);
  26 +
  27 +//
  28 +var cube = context.cube();
  29 +
  30 +//
  31 +var horizon = context.horizon();
  32 +
  33 +//
  34 +var metrics = [
  35 + cube.metric("sum(collectd(interface.if_octets.rx))").multiply(8).divide(step / 1e3),
  36 + cube.metric("sum(collectd(-interface.if_octets.tx))").multiply(8).divide(step / 1e3),
  37 + cube.metric("sum(collectd(disk.disk_ops.read))").divide(step / 1e3),
  38 + cube.metric("sum(collectd(-disk.disk_ops.write))").divide(step / 1e3),
  39 + cube.metric("max(collectd(load.shortterm))"),
  40 + cube.metric("max(collectd(load.midterm))"),
  41 + cube.metric("max(collectd(load.longterm))"),
  42 + cube.metric("max(collectd(memory).eq(type, 'wired'))"),
  43 + cube.metric("max(collectd(memory).eq(type, 'inactive'))"),
  44 + cube.metric("max(collectd(memory).eq(type, 'active'))"),
  45 + cube.metric("max(collectd(memory).eq(type, 'free'))"),
  46 + cube.metric("max(collectd(df.df_complex).eq(plugin, 'root').eq(type, 'reserved'))"),
  47 + cube.metric("max(collectd(df.df_complex).eq(plugin, 'root').eq(type, 'used'))"),
  48 + cube.metric("max(collectd(df.df_complex).eq(plugin, 'root').eq(type, 'free'))"),
  49 + cube.metric("sum(collectd(df.df_complex).eq(host, 'localhost').eq(plugin, 'root').eq(type, 'used')) / sum(collectd(df.df_complex).eq(host, 'localhost').eq(plugin, 'root').in(type, ['used', 'free']))")
  50 +];
  51 +
  52 +// Add top and bottom axes to display the time.
  53 +d3.select("body").selectAll(".axis")
  54 + .data(["top", "bottom"])
  55 + .enter().append("div")
  56 + .attr("class", function(d) { return d + " axis"; })
  57 + .each(function(d) { d3.select(this).call(context.axis().ticks(12).orient(d)); });
  58 +
  59 +// Add a mouseover rule.
  60 +d3.select("body").append("div")
  61 + .attr("class", "rule")
  62 + .call(context.rule());
  63 +
  64 +//
  65 +d3.select("body").selectAll(".horizon")
  66 + .data(metrics)
  67 + .enter().insert("div", ".bottom")
  68 + .attr("class", "horizon")
  69 + .call(horizon);
  70 +
  71 +// On mousemove, reposition the chart values to match the rule.
  72 +context.on("focus", function(i) {
  73 + d3.selectAll(".value").style("right", i == null ? null : context.size() - i + "px");
  74 +});
  75 +
  76 +// Initialize the step menu's selection.
  77 +d3.selectAll("#step option").property("selected", function() {
  78 + return this.value == step;
  79 +});
  80 +
  81 +// Update the location on step change.
  82 +d3.select("#step").on("change", function() {
  83 + window.location = "?step=" + this.value + "&" + location.search.replace(/[?&]step=[^&]*(&|$)/g, "$1").substring(1);
  84 +});
  85 +
  86 +</script>
975 static/cubism.v1.js
... ... @@ -0,0 +1,975 @@
  1 +(function(exports){
  2 +var cubism = exports.cubism = {version: "1.0.0"};
  3 +var cubism_id = 0;
  4 +function cubism_identity(d) { return d; }
  5 +cubism.option = function(name, value) {
  6 + var options = location.search.substring(1).split("&"),
  7 + i = -1,
  8 + n = options.length,
  9 + o;
  10 + while (++i < n) {
  11 + if ((o = options[i].split("="))[0] == name) {
  12 + return decodeURIComponent(o[1]);
  13 + }
  14 + }
  15 + return value;
  16 +};
  17 +cubism.context = function() {
  18 + var context = new cubism_context,
  19 + step = 1e4, // ten seconds, in milliseconds
  20 + size = 1440, // four hours at ten seconds, in pixels
  21 + start0, stop0, // the start and stop for the previous change event
  22 + start1, stop1, // the start and stop for the next prepare event
  23 + serverDelay = 5e3,
  24 + clientDelay = 5e3,
  25 + event = d3.dispatch("prepare", "beforechange", "change", "focus"),
  26 + scale = context.scale = d3.time.scale().range([0, size]),
  27 + timeout,
  28 + focus;
  29 +
  30 + function update() {
  31 + var now = Date.now();
  32 + stop0 = new Date(Math.floor((now - serverDelay - clientDelay) / step) * step);
  33 + start0 = new Date(stop0 - size * step);
  34 + stop1 = new Date(Math.floor((now - serverDelay) / step) * step);
  35 + start1 = new Date(stop1 - size * step);
  36 + scale.domain([start0, stop0]);
  37 + return context;
  38 + }
  39 +
  40 + context.start = function() {
  41 + if (timeout) clearTimeout(timeout);
  42 + var delay = +stop1 + serverDelay - Date.now();
  43 +
  44 + // If we're too late for the first prepare event, skip it.
  45 + if (delay < clientDelay) delay += step;
  46 +
  47 + timeout = setTimeout(function prepare() {
  48 + stop1 = new Date(Math.floor((Date.now() - serverDelay) / step) * step);
  49 + start1 = new Date(stop1 - size * step);
  50 + event.prepare.call(context, start1, stop1);
  51 +
  52 + setTimeout(function() {
  53 + scale.domain([start0 = start1, stop0 = stop1]);
  54 + event.beforechange.call(context, start1, stop1);
  55 + event.change.call(context, start1, stop1);
  56 + event.focus.call(context, focus);
  57 + }, clientDelay);
  58 +
  59 + timeout = setTimeout(prepare, step);
  60 + }, delay);
  61 + return context;
  62 + };
  63 +
  64 + context.stop = function() {
  65 + timeout = clearTimeout(timeout);
  66 + return context;
  67 + };
  68 +
  69 + timeout = setTimeout(context.start, 10);
  70 +
  71 + // Set or get the step interval in milliseconds.
  72 + // Defaults to ten seconds.
  73 + context.step = function(_) {
  74 + if (!arguments.length) return step;
  75 + step = +_;
  76 + return update();
  77 + };
  78 +
  79 + // Set or get the context size (the count of metric values).
  80 + // Defaults to 1440 (four hours at ten seconds).
  81 + context.size = function(_) {
  82 + if (!arguments.length) return size;
  83 + scale.range([0, size = +_]);
  84 + return update();
  85 + };
  86 +
  87 + // The server delay is the amount of time we wait for the server to compute a
  88 + // metric. This delay may result from clock skew or from delays collecting
  89 + // metrics from various hosts. Defaults to 4 seconds.
  90 + context.serverDelay = function(_) {
  91 + if (!arguments.length) return serverDelay;
  92 + serverDelay = +_;
  93 + return update();
  94 + };
  95 +
  96 + // The client delay is the amount of additional time we wait to fetch those
  97 + // metrics from the server. The client and server delay combined represent the
  98 + // age of the most recent displayed metric. Defaults to 1 second.
  99 + context.clientDelay = function(_) {
  100 + if (!arguments.length) return clientDelay;
  101 + clientDelay = +_;
  102 + return update();
  103 + };
  104 +
  105 + // Sets the focus to the specified index, and dispatches a "focus" event.
  106 + context.focus = function(i) {
  107 + event.focus.call(context, focus = i);
  108 + return context;
  109 + };
  110 +
  111 + // Add, remove or get listeners for events.
  112 + context.on = function(type, listener) {
  113 + if (arguments.length < 2) return event.on(type);
  114 +
  115 + event.on(type, listener);
  116 +
  117 + // Notify the listener of the current start and stop time, as appropriate.
  118 + // This way, metrics can make requests for data immediately,
  119 + // and likewise the axis can display itself synchronously.
  120 + if (listener != null) {
  121 + if (/^prepare(\.|$)/.test(type)) listener.call(context, start1, stop1);
  122 + if (/^beforechange(\.|$)/.test(type)) listener.call(context, start0, stop0);
  123 + if (/^change(\.|$)/.test(type)) listener.call(context, start0, stop0);
  124 + if (/^focus(\.|$)/.test(type)) listener.call(context, focus);
  125 + }
  126 +
  127 + return context;
  128 + };
  129 +
  130 + d3.select(window).on("keydown.context-" + ++cubism_id, function() {
  131 + switch (!d3.event.metaKey && d3.event.keyCode) {
  132 + case 37: // left
  133 + if (focus == null) focus = size - 1;
  134 + if (focus > 0) context.focus(--focus);
  135 + break;
  136 + case 39: // right
  137 + if (focus == null) focus = size - 2;
  138 + if (focus < size - 1) context.focus(++focus);
  139 + break;
  140 + default: return;
  141 + }
  142 + d3.event.preventDefault();
  143 + });
  144 +
  145 + return update();
  146 +};
  147 +
  148 +function cubism_context() {}
  149 +
  150 +var cubism_contextPrototype = cubism_context.prototype;
  151 +
  152 +cubism_contextPrototype.constant = function(value) {
  153 + return new cubism_metricConstant(this, +value);
  154 +};
  155 +cubism_contextPrototype.cube = function(host) {
  156 + if (!arguments.length) host = "";
  157 + var source = {},
  158 + context = this;
  159 +
  160 + source.metric = function(expression) {
  161 + return context.metric(function(start, stop, step, callback) {
  162 + d3.json(host + "/1.0/metric"
  163 + + "?expression=" + encodeURIComponent(expression)
  164 + + "&start=" + cubism_cubeFormatDate(start)
  165 + + "&stop=" + cubism_cubeFormatDate(stop)
  166 + + "&step=" + step, function(data) {
  167 + if (!data) return callback(new Error("unable to load data"));
  168 + callback(null, data.map(function(d) { return d.value; }));
  169 + });
  170 + }, expression += "");
  171 + };
  172 +
  173 + // Returns the Cube host.
  174 + source.toString = function() {
  175 + return host;
  176 + };
  177 +
  178 + return source;
  179 +};
  180 +
  181 +var cubism_cubeFormatDate = d3.time.format.iso;
  182 +cubism_contextPrototype.graphite = function(host) {
  183 + if (!arguments.length) host = "";
  184 + var source = {},
  185 + context = this;
  186 +
  187 + source.metric = function(expression) {
  188 + return context.metric(function(start, stop, step, callback) {
  189 + d3.text(host + "/render?format=raw"
  190 + + "&target=" + encodeURIComponent("alias(" + expression + ",'')")
  191 + + "&from=" + cubism_graphiteFormatDate(start - 2 * step) // off-by-two?
  192 + + "&until=" + cubism_graphiteFormatDate(stop - 1000), function(text) {
  193 + if (!text) return callback(new Error("unable to load data"));
  194 + callback(null, cubism_graphiteParse(text));
  195 + });
  196 + }, expression += "");
  197 + };
  198 +
  199 + source.find = function(pattern, callback) {
  200 + d3.json(host + "/metrics/find?format=completer"
  201 + + "&query=" + encodeURIComponent(pattern), function(result) {
  202 + if (!result) return callback(new Error("unable to find metrics"));
  203 + callback(null, result.metrics.map(function(d) { return d.path; }));
  204 + });
  205 + };
  206 +
  207 + // Returns the graphite host.
  208 + source.toString = function() {
  209 + return host;
  210 + };
  211 +
  212 + return source;
  213 +};
  214 +
  215 +// Graphite understands seconds since UNIX epoch.
  216 +function cubism_graphiteFormatDate(time) {
  217 + return Math.floor(time / 1000);
  218 +}
  219 +
  220 +// Helper method for parsing graphite's raw format.
  221 +function cubism_graphiteParse(text) {
  222 + var i = text.indexOf("|"),
  223 + meta = text.substring(0, i),
  224 + c = meta.lastIndexOf(","),
  225 + b = meta.lastIndexOf(",", c - 1),
  226 + a = meta.lastIndexOf(",", b - 1),
  227 + start = meta.substring(a + 1, b) * 1000,
  228 + step = meta.substring(c + 1) * 1000;
  229 + return text
  230 + .substring(i + 1)
  231 + .split(",")
  232 + .slice(1) // the first value is always None?
  233 + .map(function(d) { return +d; });
  234 +}
  235 +function cubism_metric(context) {
  236 + if (!(context instanceof cubism_context)) throw new Error("invalid context");
  237 + this.context = context;
  238 +}
  239 +
  240 +var cubism_metricPrototype = cubism_metric.prototype;
  241 +
  242 +cubism_metricPrototype.valueAt = function() {
  243 + return NaN;
  244 +};
  245 +
  246 +cubism_metricPrototype.extent = function() {
  247 + var i = 0,
  248 + n = this.context.size(),
  249 + value,
  250 + min = Infinity,
  251 + max = -Infinity;
  252 + while (++i < n) {
  253 + value = this.valueAt(i);
  254 + if (value < min) min = value;
  255 + if (value > max) max = value;
  256 + }
  257 + return [min, max];
  258 +};
  259 +
  260 +cubism_metricPrototype.on = function(type, listener) {
  261 + return arguments.length < 2 ? null : this;
  262 +};
  263 +
  264 +cubism_metricPrototype.shift = function() {
  265 + return this;
  266 +};
  267 +
  268 +cubism_metricPrototype.on = function() {
  269 + return arguments.length < 2 ? null : this;
  270 +};
  271 +
  272 +cubism_contextPrototype.metric = function(request, name) {
  273 + var context = this,
  274 + metric = new cubism_metric(context),
  275 + id = ".metric-" + ++cubism_id,
  276 + start = -Infinity,
  277 + stop,
  278 + step = context.step(),
  279 + size = context.size(),
  280 + values = [],
  281 + event = d3.dispatch("change"),
  282 + listening = 0,
  283 + fetching;
  284 +
  285 + // Prefetch new data into a temporary array.
  286 + function prepare(start1, stop) {
  287 + var steps = Math.min(size, Math.round((start1 - start) / step));
  288 + if (!steps || fetching) return; // already fetched, or fetching!
  289 + fetching = true;
  290 + steps = Math.min(size, steps + cubism_metricOverlap);
  291 + var start0 = new Date(stop - steps * step);
  292 + request(start0, stop, step, function(error, data) {
  293 + fetching = false;
  294 + if (error) return console.warn(error);
  295 + var i = isFinite(start) ? Math.round((start0 - start) / step) : 0;
  296 + for (var j = 0, m = data.length; j < m; ++j) values[j + i] = data[j];
  297 + event.change.call(metric, start, stop);
  298 + });
  299 + }
  300 +
  301 + // When the context changes, switch to the new data, ready-or-not!
  302 + function beforechange(start1, stop1) {
  303 + if (!isFinite(start)) start = start1;
  304 + values.splice(0, Math.max(0, Math.min(size, Math.round((start1 - start) / step))));
  305 + start = start1;
  306 + stop = stop1;
  307 + }
  308 +
  309 + //
  310 + metric.valueAt = function(i) {
  311 + return values[i];
  312 + };
  313 +
  314 + //
  315 + metric.shift = function(offset) {
  316 + return context.metric(cubism_metricShift(request, +offset));
  317 + };
  318 +
  319 + //
  320 + metric.on = function(type, listener) {
  321 + if (!arguments.length) return event.on(type);
  322 +
  323 + // If there are no listeners, then stop listening to the context,
  324 + // and avoid unnecessary fetches.
  325 + if (listener == null) {
  326 + if (event.on(type) != null && --listening == 0) {
  327 + context.on("prepare" + id, null).on("beforechange" + id, null);
  328 + }
  329 + } else {
  330 + if (event.on(type) == null && ++listening == 1) {
  331 + context.on("prepare" + id, prepare).on("beforechange" + id, beforechange);
  332 + }
  333 + }
  334 +
  335 + event.on(type, listener);
  336 +
  337 + // Notify the listener of the current start and stop time, as appropriate.
  338 + // This way, charts can display synchronous metrics immediately.
  339 + if (listener != null) {
  340 + if (/^change(\.|$)/.test(type)) listener.call(context, start, stop);
  341 + }
  342 +
  343 + return metric;
  344 + };
  345 +
  346 + //
  347 + if (arguments.length > 1) metric.toString = function() {
  348 + return name;
  349 + };
  350 +
  351 + return metric;
  352 +};
  353 +
  354 +// Number of metric to refetch each period, in case of lag.
  355 +var cubism_metricOverlap = 6;
  356 +
  357 +// Wraps the specified request implementation, and shifts time by the given offset.
  358 +function cubism_metricShift(request, offset) {
  359 + return function(start, stop, step, callback) {
  360 + request(new Date(+start + offset), new Date(+stop + offset), step, callback);
  361 + };
  362 +}
  363 +function cubism_metricConstant(context, value) {
  364 + cubism_metric.call(this, context);
  365 + value = +value;
  366 + var name = value + "";
  367 + this.valueOf = function() { return value; };
  368 + this.toString = function() { return name; };
  369 +}
  370 +
  371 +var cubism_metricConstantPrototype = cubism_metricConstant.prototype = Object.create(cubism_metric.prototype);
  372 +
  373 +cubism_metricConstantPrototype.valueAt = function() {
  374 + return +this;
  375 +};
  376 +
  377 +cubism_metricConstantPrototype.extent = function() {
  378 + return [+this, +this];
  379 +};
  380 +function cubism_metricOperator(name, operate) {
  381 +
  382 + function cubism_metricOperator(left, right) {
  383 + if (!(right instanceof cubism_metric)) right = new cubism_metricConstant(left.context, right);
  384 + else if (left.context !== right.context) throw new Error("mismatch context");
  385 + cubism_metric.call(this, left.context);
  386 + this.left = left;
  387 + this.right = right;
  388 + this.toString = function() { return left + " " + name + " " + right; };
  389 + }
  390 +
  391 + var cubism_metricOperatorPrototype = cubism_metricOperator.prototype = Object.create(cubism_metric.prototype);
  392 +
  393 + cubism_metricOperatorPrototype.valueAt = function(i) {
  394 + return operate(this.left.valueAt(i), this.right.valueAt(i));
  395 + };
  396 +
  397 + cubism_metricOperatorPrototype.shift = function(offset) {
  398 + return new cubism_metricOperator(this.left.shift(offset), this.right.shift(offset));
  399 + };
  400 +
  401 + cubism_metricOperatorPrototype.on = function(type, listener) {
  402 + if (arguments.length < 2) return this.left.on(type);
  403 + this.left.on(type, listener);
  404 + this.right.on(type, listener);
  405 + return this;
  406 + };
  407 +
  408 + return function(right) {
  409 + return new cubism_metricOperator(this, right);
  410 + };
  411 +}
  412 +
  413 +cubism_metricPrototype.add = cubism_metricOperator("+", function(left, right) {
  414 + return left + right;
  415 +});
  416 +
  417 +cubism_metricPrototype.subtract = cubism_metricOperator("-", function(left, right) {
  418 + return left - right;
  419 +});
  420 +
  421 +cubism_metricPrototype.multiply = cubism_metricOperator("*", function(left, right) {
  422 + return left * right;
  423 +});
  424 +
  425 +cubism_metricPrototype.divide = cubism_metricOperator("/", function(left, right) {
  426 + return left / right;
  427 +});
  428 +cubism_contextPrototype.horizon = function() {
  429 + var context = this,
  430 + mode = "offset",
  431 + buffer = document.createElement("canvas"),
  432 + width = buffer.width = context.size(),
  433 + height = buffer.height = 30,
  434 + scale = d3.scale.linear().interpolate(d3.interpolateRound),
  435 + metric = cubism_identity,
  436 + extent = null,
  437 + title = cubism_identity,
  438 + format = d3.format(".2s"),
  439 + colors = ["#08519c","#3182bd","#6baed6","#bdd7e7","#bae4b3","#74c476","#31a354","#006d2c"];
  440 +
  441 + function horizon(selection) {
  442 +
  443 + selection
  444 + .on("mousemove.horizon", function() { context.focus(d3.mouse(this)[0]); })
  445 + .on("mouseout.horizon", function() { context.focus(null); });
  446 +
  447 + selection.append("canvas")
  448 + .attr("width", width)
  449 + .attr("height", height);
  450 +
  451 + selection.append("span")
  452 + .attr("class", "title")
  453 + .text(title);
  454 +
  455 + selection.append("span")
  456 + .attr("class", "value");
  457 +
  458 + selection.each(function(d, i) {
  459 + var that = this,
  460 + id = ++cubism_id,
  461 + metric_ = typeof metric === "function" ? metric.call(that, d, i) : metric,
  462 + colors_ = typeof colors === "function" ? colors.call(that, d, i) : colors,
  463 + extent_ = typeof extent === "function" ? extent.call(that, d, i) : extent,
  464 + start = -Infinity,
  465 + step = context.step(),
  466 + canvas = d3.select(that).select("canvas"),
  467 + span = d3.select(that).select(".value"),
  468 + max_,
  469 + m = colors_.length >> 1,
  470 + ready;
  471 +
  472 + canvas.datum({id: id, metric: metric_});
  473 + canvas = canvas.node().getContext("2d");
  474 +
  475 + function change(start1, stop) {
  476 + canvas.save();
  477 +
  478 + // compute the new extent and ready flag
  479 + var extent = metric_.extent();
  480 + ready = extent.every(isFinite);
  481 + if (extent_ != null) extent = extent_;
  482 +
  483 + // if this is an update (with no extent change), copy old values!
  484 + var i0 = 0, max = Math.max(-extent[0], extent[1]);
  485 + if (this === context) {
  486 + if (max == max_) {
  487 + i0 = width - cubism_metricOverlap;
  488 + var dx = (start1 - start) / step;
  489 + if (dx < width) {
  490 + var canvas0 = buffer.getContext("2d");
  491 + canvas0.clearRect(0, 0, width, height);
  492 + canvas0.drawImage(canvas.canvas, dx, 0, width - dx, height, 0, 0, width - dx, height);
  493 + canvas.clearRect(0, 0, width, height);
  494 + canvas.drawImage(canvas0.canvas, 0, 0);
  495 + }
  496 + }
  497 + start = start1;
  498 + }
  499 +
  500 + // update the domain
  501 + scale.domain([0, max_ = max]);
  502 +
  503 + // clear for the new data
  504 + canvas.clearRect(i0, 0, width - i0, height);
  505 +
  506 + // record whether there are negative values to display
  507 + var negative;
  508 +
  509 + // positive bands
  510 + for (var j = 0; j < m; ++j) {
  511 + canvas.fillStyle = colors_[m + j];
  512 +
  513 + // Adjust the range based on the current band index.
  514 + var y0 = (j - m + 1) * height;
  515 + scale.range([m * height + y0, y0]);
  516 + y0 = scale(0);
  517 +
  518 + for (var i = i0, n = width, y1; i < n; ++i) {
  519 + y1 = metric_.valueAt(i);
  520 + if (y1 <= 0) { negative = true; continue; }
  521 + canvas.fillRect(i, y1 = scale(y1), 1, y0 - y1);
  522 + }
  523 + }
  524 +
  525 + if (negative) {
  526 + // enable offset mode
  527 + if (mode === "offset") {
  528 + canvas.translate(0, height);
  529 + canvas.scale(1, -1);
  530 + }
  531 +
  532 + // negative bands
  533 + for (var j = 0; j < m; ++j) {
  534 + canvas.fillStyle = colors_[m - 1 - j];
  535 +
  536 + // Adjust the range based on the current band index.
  537 + var y0 = (j - m + 1) * height;
  538 + scale.range([m * height + y0, y0]);
  539 + y0 = scale(0);
  540 +
  541 + for (var i = i0, n = width, y1; i < n; ++i) {
  542 + y1 = metric_.valueAt(i);
  543 + if (y1 >= 0) continue;
  544 + canvas.fillRect(i, scale(-y1), 1, y0 - scale(-y1));
  545 + }
  546 + }
  547 + }
  548 +
  549 + canvas.restore();
  550 + }
  551 +
  552 + function focus(i) {
  553 + if (i == null) i = width - 1;
  554 + var value = metric_.valueAt(i);
  555 + span.datum(value).text(isNaN(value) ? null : format);
  556 + }
  557 +
  558 + // Update the chart when the context changes.
  559 + context.on("change.horizon-" + id, change);
  560 + context.on("focus.horizon-" + id, focus);
  561 +
  562 + // Display the first metric change immediately,
  563 + // but defer subsequent updates to the canvas change.
  564 + // Note that someone still needs to listen to the metric,
  565 + // so that it continues to update automatically.
  566 + metric_.on("change.horizon-" + id, function(start, stop) {
  567 + change(start, stop), focus();
  568 + if (ready) metric_.on("change.horizon-" + id, cubism_identity);
  569 + });
  570 + });
  571 + }
  572 +
  573 + horizon.remove = function(selection) {
  574 +
  575 + selection
  576 + .on("mousemove.horizon", null)
  577 + .on("mouseout.horizon", null);
  578 +
  579 + selection.selectAll("canvas")
  580 + .each(remove)
  581 + .remove();
  582 +
  583 + selection.selectAll(".title,.value")
  584 + .remove();
  585 +
  586 + function remove(d) {
  587 + d.metric.on("change.horizon-" + d.id, null);
  588 + context.on("change.horizon-" + d.id, null);
  589 + context.on("focus.horizon-" + d.id, null);
  590 + }
  591 + };
  592 +
  593 + horizon.mode = function(_) {
  594 + if (!arguments.length) return mode;
  595 + mode = _ + "";
  596 + return horizon;
  597 + };
  598 +
  599 + horizon.height = function(_) {
  600 + if (!arguments.length) return height;
  601 + buffer.height = height = +_;
  602 + return horizon;
  603 + };
  604 +
  605 + horizon.metric = function(_) {
  606 + if (!arguments.length) return metric;
  607 + metric = _;
  608 + return horizon;
  609 + };
  610 +
  611 + horizon.scale = function(_) {
  612 + if (!arguments.length) return scale;
  613 + scale = _;
  614 + return horizon;
  615 + };
  616 +
  617 + horizon.extent = function(_) {
  618 + if (!arguments.length) return extent;
  619 + extent = _;
  620 + return horizon;
  621 + };
  622 +
  623 + horizon.title = function(_) {
  624 + if (!arguments.length) return title;
  625 + title = _;
  626 + return horizon;
  627 + };
  628 +
  629 + horizon.format = function(_) {
  630 + if (!arguments.length) return format;
  631 + format = _;
  632 + return horizon;
  633 + };
  634 +
  635 + horizon.colors = function(_) {
  636 + if (!arguments.length) return colors;
  637 + colors = _;
  638 + return horizon;
  639 + };
  640 +
  641 + return horizon;
  642 +};
  643 +cubism_contextPrototype.comparison = function() {
  644 + var context = this,
  645 + width = context.size(),
  646 + height = 120,
  647 + scale = d3.scale.linear().interpolate(d3.interpolateRound),
  648 + primary = function(d) { return d[0]; },
  649 + secondary = function(d) { return d[1]; },
  650 + extent = null,
  651 + title = cubism_identity,
  652 + formatPrimary = cubism_comparisonPrimaryFormat,
  653 + formatChange = cubism_comparisonChangeFormat,
  654 + colors = ["#9ecae1", "#225b84", "#a1d99b", "#22723a"],
  655 + strokeWidth = 1.5;
  656 +
  657 + function comparison(selection) {
  658 +
  659 + selection
  660 + .on("mousemove.comparison", function() { context.focus(d3.mouse(this)[0]); })
  661 + .on("mouseout.comparison", function() { context.focus(null); });
  662 +
  663 + selection.append("canvas")
  664 + .attr("width", width)
  665 + .attr("height", height);
  666 +
  667 + selection.append("span")
  668 + .attr("class", "title")
  669 + .text(title);
  670 +
  671 + selection.append("span")
  672 + .attr("class", "value primary");
  673 +
  674 + selection.append("span")
  675 + .attr("class", "value change");
  676 +
  677 + selection.each(function(d, i) {
  678 + var that = this,
  679 + id = ++cubism_id,
  680 + primary_ = typeof primary === "function" ? primary.call(that, d, i) : primary,
  681 + secondary_ = typeof secondary === "function" ? secondary.call(that, d, i) : secondary,
  682 + extent_ = typeof extent === "function" ? extent.call(that, d, i) : extent,
  683 + div = d3.select(that),
  684 + canvas = div.select("canvas"),
  685 + spanPrimary = div.select(".value.primary"),
  686 + spanChange = div.select(".value.change"),
  687 + ready;
  688 +
  689 + canvas.datum({id: id, primary: primary_, secondary: secondary_});
  690 + canvas = canvas.node().getContext("2d");
  691 +
  692 + function change(start, stop) {
  693 + canvas.save();
  694 + canvas.clearRect(0, 0, width, height);
  695 +
  696 + // update the scale
  697 + var primaryExtent = primary_.extent(),
  698 + secondaryExtent = secondary_.extent(),
  699 + extent = extent_ == null ? primaryExtent : extent_;
  700 + scale.domain(extent).range([height, 0]);
  701 + ready = primaryExtent.concat(secondaryExtent).every(isFinite);
  702 +
  703 + // consistent overplotting
  704 + var round = start / context.step() & 1
  705 + ? cubism_comparisonRoundOdd
  706 + : cubism_comparisonRoundEven;
  707 +
  708 + // positive changes
  709 + canvas.fillStyle = colors[2];
  710 + for (var i = 0, n = width; i < n; ++i) {
  711 + var y0 = scale(primary_.valueAt(i)),
  712 + y1 = scale(secondary_.valueAt(i));
  713 + if (y0 < y1) canvas.fillRect(round(i), y0, 1, y1 - y0);
  714 + }
  715 +
  716 + // negative changes
  717 + canvas.fillStyle = colors[0];
  718 + for (i = 0; i < n; ++i) {
  719 + var y0 = scale(primary_.valueAt(i)),
  720 + y1 = scale(secondary_.valueAt(i));
  721 + if (y0 > y1) canvas.fillRect(round(i), y1, 1, y0 - y1);
  722 + }
  723 +
  724 + // positive values
  725 + canvas.fillStyle = colors[3];
  726 + for (i = 0; i < n; ++i) {
  727 + var y0 = scale(primary_.valueAt(i)),
  728 + y1 = scale(secondary_.valueAt(i));
  729 + if (y0 <= y1) canvas.fillRect(round(i), y0, 1, strokeWidth);
  730 + }
  731 +
  732 + // negative values
  733 + canvas.fillStyle = colors[1];
  734 + for (i = 0; i < n; ++i) {
  735 + var y0 = scale(primary_.valueAt(i)),
  736 + y1 = scale(secondary_.valueAt(i));
  737 + if (y0 > y1) canvas.fillRect(round(i), y0 - strokeWidth, 1, strokeWidth);
  738 + }
  739 +
  740 + canvas.restore();
  741 + }
  742 +
  743 + function focus(i) {
  744 + if (i == null) i = width - 1;
  745 + var valuePrimary = primary_.valueAt(i),
  746 + valueSecondary = secondary_.valueAt(i),
  747 + valueChange = (valuePrimary - valueSecondary) / valueSecondary;
  748 +
  749 + spanPrimary
  750 + .datum(valuePrimary)
  751 + .text(isNaN(valuePrimary) ? null : formatPrimary);
  752 +
  753 + spanChange
  754 + .datum(valueChange)
  755 + .text(isNaN(valueChange) ? null : formatChange)
  756 + .attr("class", "value change " + (valueChange > 0 ? "positive" : valueChange < 0 ? "negative" : ""));
  757 + }
  758 +
  759 + // Display the first primary change immediately,
  760 + // but defer subsequent updates to the context change.
  761 + // Note that someone still needs to listen to the metric,
  762 + // so that it continues to update automatically.
  763 + primary_.on("change.comparison-" + id, firstChange);
  764 + secondary_.on("change.comparison-" + id, firstChange);
  765 + function firstChange(start, stop) {
  766 + change(start, stop), focus();
  767 + if (ready) {
  768 + primary_.on("change.comparison-" + id, cubism_identity);
  769 + secondary_.on("change.comparison-" + id, cubism_identity);
  770 + }
  771 + }
  772 +
  773 + // Update the chart when the context changes.
  774 + context.on("change.comparison-" + id, change);
  775 + context.on("focus.comparison-" + id, focus);
  776 + });
  777 + }
  778 +
  779 + comparison.remove = function(selection) {
  780 +
  781 + selection
  782 + .on("mousemove.comparison", null)
  783 + .on("mouseout.comparison", null);
  784 +
  785 + selection.selectAll("canvas")
  786 + .each(remove)
  787 + .remove();
  788 +
  789 + selection.selectAll(".title,.value")
  790 + .remove();
  791 +
  792 + function remove(d) {
  793 + d.primary.on("change.comparison-" + d.id, null);
  794 + d.secondary.on("change.comparison-" + d.id, null);
  795 + context.on("change.comparison-" + d.id, null);
  796 + context.on("focus.comparison-" + d.id, null);
  797 + }
  798 + };
  799 +
  800 + comparison.height = function(_) {
  801 + if (!arguments.length) return height;
  802 + height = +_;
  803 + return comparison;
  804 + };
  805 +
  806 + comparison.primary = function(_) {
  807 + if (!arguments.length) return primary;
  808 + primary = _;
  809 + return comparison;
  810 + };
  811 +
  812 + comparison.secondary = function(_) {
  813 + if (!arguments.length) return secondary;
  814 + secondary = _;
  815 + return comparison;
  816 + };
  817 +
  818 + comparison.scale = function(_) {
  819 + if (!arguments.length) return scale;
  820 + scale = _;
  821 + return comparison;
  822 + };
  823 +
  824 + comparison.extent = function(_) {
  825 + if (!arguments.length) return extent;
  826 + extent = _;
  827 + return comparison;
  828 + };
  829 +
  830 + comparison.title = function(_) {
  831 + if (!arguments.length) return title;
  832 + title = _;
  833 + return comparison;
  834 + };
  835 +
  836 + comparison.formatPrimary = function(_) {
  837 + if (!arguments.length) return formatPrimary;
  838 + formatPrimary = _;
  839 + return comparison;
  840 + };
  841 +
  842 + comparison.formatChange = function(_) {
  843 + if (!arguments.length) return formatChange;
  844 + formatChange = _;
  845 + return comparison;
  846 + };
  847 +
  848 + comparison.colors = function(_) {
  849 + if (!arguments.length) return colors;
  850 + colors = _;
  851 + return comparison;
  852 + };
  853 +
  854 + comparison.strokeWidth = function(_) {
  855 + if (!arguments.length) return strokeWidth;
  856 + strokeWidth = _;
  857 + return comparison;
  858 + };
  859 +
  860 + return comparison;
  861 +};
  862 +
  863 +var cubism_comparisonPrimaryFormat = d3.format(".2s"),
  864 + cubism_comparisonChangeFormat = d3.format("+.0%");
  865 +
  866 +function cubism_comparisonRoundEven(i) {
  867 + return i & 0xfffffe;
  868 +}
  869 +
  870 +function cubism_comparisonRoundOdd(i) {
  871 + return ((i + 1) & 0xfffffe) - 1;
  872 +}
  873 +cubism_contextPrototype.axis = function() {
  874 + var context = this,
  875 + scale = context.scale,
  876 + axis_ = d3.svg.axis().scale(scale),
  877 + format = context.step() < 6e4 ? cubism_axisFormatSeconds : cubism_axisFormatMinutes;
  878 +
  879 + function axis(selection) {
  880 + var id = ++cubism_id,
  881 + tick;
  882 +
  883 + var g = selection.append("svg")
  884 + .datum({id: id})
  885 + .attr("width", context.size())
  886 + .attr("height", Math.max(28, -axis.tickSize()))
  887 + .append("g")
  888 + .attr("transform", "translate(0," + (axis_.orient() === "top" ? 27 : 4) + ")")
  889 + .call(axis_);
  890 +
  891 + context.on("change.axis-" + id, function() {
  892 + g.call(axis_);
  893 + if (!tick) tick = cloneTick();
  894 + });
  895 +
  896 + context.on("focus.axis-" + id, function(i) {
  897 + if (tick) {
  898 + if (i == null) {
  899 + tick.style("display", "none");
  900 + g.selectAll("text").style("fill-opacity", null);
  901 + } else {
  902 + tick.style("display", null).attr("x", i).text(format(scale.invert(i)));
  903 + var dx = tick.node().getComputedTextLength() + 6;
  904 + g.selectAll("text").style("fill-opacity", function(d) { return Math.abs(scale(d) - i) < dx ? 0 : 1; });
  905 + }
  906 + }
  907 + });
  908 +
  909 + function cloneTick() {
  910 + return g.select(function() { return this.appendChild(g.select("text").node().cloneNode(true)); })
  911 + .style("display", "none")
  912 + .text(null);
  913 + }
  914 + }
  915 +
  916 + axis.remove = function(selection) {
  917 +
  918 + selection.selectAll("svg")
  919 + .each(remove)
  920 + .remove();
  921 +
  922 + function remove(d) {
  923 + context.on("change.axis-" + d.id, null);
  924 + context.on("focus.axis-" + d.id, null);
  925 + }
  926 + };
  927 +
  928 + return d3.rebind(axis, axis_,
  929 + "orient",
  930 + "ticks",
  931 + "tickSubdivide",
  932 + "tickSize",
  933 + "tickPadding",
  934 + "tickFormat");
  935 +};
  936 +
  937 +var cubism_axisFormatSeconds = d3.time.format("%I:%M:%S %p"),
  938 + cubism_axisFormatMinutes = d3.time.format("%I:%M %p");
  939 +cubism_contextPrototype.rule = function() {
  940 + var context = this;
  941 +
  942 + function rule(selection) {
  943 + var id = ++cubism_id;
  944 +
  945 + var line = selection.append("div")
  946 + .datum({id: id})
  947 + .attr("class", "line")
  948 + .style("position", "fixed")
  949 + .style("top", 0)
  950 + .style("right", 0)
  951 + .style("bottom", 0)
  952 + .style("width", "1px")
  953 + .style("pointer-events", "none");
  954 +
  955 + context.on("focus.rule-" + id, function(i) {
  956 + line
  957 + .style("display", i == null ? "none" : null)
  958 + .style("left", function() { return this.parentNode.getBoundingClientRect().left + i + "px"; });
  959 + });
  960 + }
  961 +
  962 + rule.remove = function(selection) {
  963 +
  964 + selection.selectAll(".line")
  965 + .each(remove)
  966 + .remove();
  967 +
  968 + function remove(d) {
  969 + context.on("focus.rule-" + d.id, null);
  970 + }
  971 + };
  972 +
  973 + return rule;
  974 +};
  975 +})(this);
1  static/cubism.v1.min.js
... ... @@ -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}var b=a.cubism={version:"1.0.0"},c=0;b.option=function(a,b){var c=location.search.substring(1).split("&"),d=-1,e=c.length,f;while(++d<e)if((f=c[d].split("="))[0]==a)return decodeURIComponent(f[1]);return 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=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){return c.metric(function(c,d,e,f){d3.text(a+"/render?format=raw"+"&target="+encodeURIComponent("alias("+b+",'')")+"&from="+h(c-2*e)+"&until="+h(d-1e3),function(a){if(!a)return f(new Error("unable to load data"));f(null,i(a))})},b+="")},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;k.valueAt=function(){return NaN},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(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(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){function k(){return j.select(function(){return this.appendChild(j.select("text").node().cloneNode(!0))}).style("display","none").text(null)}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=k())}),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:v;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");f.rule=function(){function b(b){var d=++c,e=b.append("div").datum({id:d}).attr("class","line").style("position","fixed").style("top",0).style("right",0).style("bottom",0).style("width","1px").style("pointer-events","none");a.on("focus.rule-"+d,function(a){e.style("display",a==null?"none":null).style("left",function(){return this.parentNode.getBoundingClientRect().left+a+"px"})})}var a=this;return b.remove=function(b){function c(b){a.on("focus.rule-"+b.id,null)}b.selectAll(".line").each(c).remove()},b}})(this);
9,382 static/d3.v2.js
9,382 additions, 0 deletions not shown
4 static/d3.v2.min.js
4 additions, 0 deletions not shown
84 static/index.html
... ... @@ -0,0 +1,84 @@
  1 +<!DOCTYPE html>
  2 +<meta charset="utf-8">
  3 +<title>Cube</title>
  4 +<style>
  5 +
  6 +@import url(style.css);
  7 +
  8 +</style>
  9 +<select id="step">
  10 + <option value="1e4">10 seconds</option>
  11 + <option value="6e4">1 minute</option>
  12 + <option value="3e5">5 minutes</option>
  13 +</select>
  14 +<script src="d3.v2.js"></script>
  15 +<script src="cubism.v1.js"></script>
  16 +<script>
  17 +
  18 +//
  19 +var step = +cubism.option("step", 1e4);
  20 +
  21 +//
  22 +var context = cubism.context()
  23 + .step(step)
  24 + .size(1440);
  25 +
  26 +//
  27 +var cube = context.cube();
  28 +
  29 +// Add top and bottom axes to display the time.
  30 +d3.select("body").selectAll(".axis")
  31 + .data(["top", "bottom"])
  32 + .enter().append("div")
  33 + .attr("class", function(d) { return d + " axis"; })
  34 + .each(function(d) { d3.select(this).call(context.axis().ticks(12).orient(d)); });
  35 +
  36 +// Add a mouseover rule.
  37 +d3.select("body").append("div")
  38 + .attr("class", "rule")
  39 + .call(context.rule());
  40 +
  41 +//
  42 +d3.select("body").insert("div", ".bottom")
  43 + .attr("class", "group")
  44 + .call(function() { this.append("header").text("performance"); })
  45 + .selectAll(".horizon")
  46 + .data([
  47 + {title: "load", metric: cube.metric("sum(cube_compute(ms))").divide(step)},
  48 + {title: "median latency (ms)", metric: cube.metric("median(cube_compute(ms))")}
  49 + ])
  50 + .enter().append("div")
  51 + .attr("class", "horizon")
  52 + .call(context.horizon()
  53 + .title(function(d) { return d.title; })
  54 + .metric(function(d) { return d.metric; }));
  55 +
  56 +//
  57 +d3.json("/1.0/types", function(types) {
  58 + d3.select("body").insert("div", ".bottom")
  59 + .attr("class", "group")
  60 + .call(function() { this.append("header").text("incoming events (/s)"); })
  61 + .selectAll(".horizon")
  62 + .data(types)
  63 + .enter().append("div")
  64 + .attr("class", "horizon")
  65 + .call(context.horizon()
  66 + .metric(function(d) { return cube.metric("sum(" + d + ")").divide(step / 1e3); }));
  67 +});
  68 +
  69 +// On mousemove, reposition the chart values to match the rule.
  70 +context.on("focus", function(i) {
  71 + d3.selectAll(".value").style("right", i == null ? null : context.size() - i + "px");
  72 +});
  73 +
  74 +// Initialize the step menu's selection.
  75 +d3.selectAll("#step option").property("selected", function() {
  76 + return this.value == step;
  77 +});
  78 +
  79 +// Update the location on step change.
  80 +d3.select("#step").on("change", function() {
  81 + window.location = "?step=" + this.value + "&" + location.search.replace(/[?&]step=[^&]*(&|$)/g, "$1").substring(1);
  82 +});
  83 +
  84 +</script>
75 static/random/index.html
... ... @@ -0,0 +1,75 @@
  1 +<!DOCTYPE html>
  2 +<meta charset="utf-8">
  3 +<title>Random</title>
  4 +<style>
  5 +
  6 +@import url(../style.css);
  7 +
  8 +</style>
  9 +<select id="step">
  10 + <option value="1e4">10 seconds</option>
  11 + <option value="6e4">1 minute</option>
  12 + <option value="3e5">5 minutes</option>
  13 +</select>
  14 +<script src="../d3.v2.js"></script>
  15 +<script src="../cubism.v1.js"></script>
  16 +<script>
  17 +
  18 +//
  19 +var step = +cubism.option("step", 1e4);
  20 +
  21 +//
  22 +var context = cubism.context()
  23 + .step(step)
  24 + .size(1440);
  25 +
  26 +//
  27 +var cube = context.cube();
  28 +
  29 +//
  30 +var horizon = context.horizon();
  31 +
  32 +//
  33 +var metrics = [
  34 + cube.metric("sum(random)"),
  35 + cube.metric("sum(random(value))").divide(cube.metric("sum(random)")),
  36 + cube.metric("median(random(value))"),
  37 + cube.metric("min(random(value))"),
  38 + cube.metric("max(random(value))")
  39 +];
  40 +
  41 +// Add top and bottom axes to display the time.
  42 +d3.select("body").selectAll(".axis")
  43 + .data(["top", "bottom"])
  44 + .enter().append("div")
  45 + .attr("class", function(d) { return d + " axis"; })
  46 + .each(function(d) { d3.select(this).call(context.axis().ticks(12).orient(d)); });
  47 +
  48 +// Add a mouseover rule.
  49 +d3.select("body").append("div")
  50 + .attr("class", "rule")
  51 + .call(context.rule());
  52 +
  53 +//
  54 +d3.select("body").selectAll(".horizon")
  55 + .data(metrics)
  56 + .enter().insert("div", ".bottom")
  57 + .attr("class", "horizon")
  58 + .call(horizon);