Skip to content
Browse files

Checkpoint horizon chart.

  • Loading branch information...
1 parent ee02ad4 commit 3052036411e937a1845847921c3e0e3a0c3c7136 @mbostock mbostock committed Mar 29, 2012
Showing with 203 additions and 5 deletions.
  1. +2 −0 Makefile
  2. +100 −2 cubism.js
  3. +1 −1 cubism.min.js
  4. +94 −0 src/horizon.js
  5. +1 −0 src/identity.js
  6. +2 −0 src/metric.js
  7. +3 −2 src/source.js
View
2 Makefile
@@ -7,6 +7,7 @@ all: cubism.min.js package.json
cubism.js: \
src/cubism.js \
+ src/identity.js \
src/source.js \
src/metric.js \
src/cube.js \
@@ -17,6 +18,7 @@ cubism.js: \
src/multiply.js \
src/divide.js \
src/constant.js \
+ src/horizon.js \
Makefile
%.min.js: %.js Makefile
View
102 cubism.js
@@ -1,10 +1,12 @@
(function(exports){
var cubism = exports.cubism = {version: "0.0.1"};
+function cubism_identity(d) { return d; }
function cubism_source(request) {
var source = {};
source.metric = function(context, expression) {
var metric = new cubism_metric,
+ id = ++cubism_metricId,
last,
offset,
offsetTime = context.start(),
@@ -34,13 +36,13 @@ function cubism_source(request) {
if (delay > 1000) timeout = setTimeout(refresh, delay);
// When the context changes, delay the request for a half-interval.
- context.on("change", function() {
+ context.on("change.metric-" + id, function() {
if (timeout) clearTimeout(timeout);
timeout = setTimeout(refresh, context.step() / 2);
});
// When the context is closed, cancel any pending refresh.
- context.on("cancel", function() {
+ context.on("cancel.metric-" + id, function() {
timeout = clearTimeout(timeout);
});
@@ -73,6 +75,8 @@ function cubism_source(request) {
// Number of metric to refetch each period, in case of lag.
var cubism_sourceOverlap = 6;
function cubism_metric() {}
+
+var cubism_metricId = 0;
cubism.cube = function(host) {
if (!arguments.length) host = "";
var iso = d3.time.format.iso;
@@ -299,4 +303,98 @@ function cubism_constant(size, value) {
metric.size = function() { return size; };
return metric;
}
+cubism_context.prototype.horizon = function() {
+ var mode = "offset",
+ width = this.size(),
+ height = 40,
+ metric = cubism_identity,
+ title = cubism_identity,
+ format = d3.format(".2s");
+
+ // TODO configurable extent
+ // TODO configurable positive colors
+ // TODO configurable negative colors
+ // TODO configurable bands
+
+ function horizon(selection) {
+ selection.each(function(d, i) {
+ var div = d3.select(this),
+ canvas = div.select("canvas"),
+ tspan = div.select(".title"),
+ vspan = div.select(".value"),
+ metric_ = typeof metric === "function" ? metric.call(this, d, i) : metric,
+ title_ = typeof title === "function" ? title.call(this, d, i) : title;
+
+ if (canvas.empty()) {
+ canvas = div.append("canvas").attr("width", width).attr("height", height);
+ tspan = div.append("span").attr("class", "title");
+ vspan = div.append("span").attr("class", "value");
+ }
+
+ var y = d3.scale.linear()
+ .domain([0, Math.max(-metric_.extent()[0], metric_.extent()[1])])
+ .rangeRound([height, 0]);
+
+ var context = canvas.node().getContext("2d");
+ context.clearRect(0, 0, width, height);
+
+ context.fillStyle = "steelblue";
+ for (var i = 0, n = width, v; i < n; ++i) {
+ var v = metric_.valueAt(i);
+ if (v <= 0) continue;
+ context.fillRect(i, v = y(v), 1, height - v);
+ }
+
+ context.fillStyle = "brown";
+ if (mode == "offset") {
+ for (var i = 0, n = width, v; i < n; ++i) {
+ var v = metric_.valueAt(i);
+ if (v >= 0) continue;
+ context.fillRect(i, 0, 1, height - y(-v));
+ }
+ } else {
+ for (var i = 0, n = width, v; i < n; ++i) {
+ var v = metric_.valueAt(i);
+ if (v >= 0) continue;
+ context.fillRect(i, v = y(-v), 1, height - v);
+ }
+ }
+
+ tspan.text(title_);
+ vspan.datum(v).text(isNaN(v) ? null : format);
+ });
+ }
+
+ horizon.mode = function(_) {
+ if (!arguments.length) return mode;
+ mode = _ + "";
+ return horizon;
+ };
+
+ horizon.height = function(_) {
+ if (!arguments.length) return height;
+ height = +_;
+ return horizon;
+ };
+
+ horizon.metric = function(_) {
+ if (!arguments.length) return metric;
+ metric = _;
+ return horizon;
+ };
+
+ horizon.title = function(_) {
+ if (!arguments.length) return title;
+ title = _;
+ return horizon;
+ };
+
+ horizon.format = function(_) {
+ if (!arguments.length) return format;
+ format = _;
+ return horizon;
+ };
+
+ return horizon;
+};
})(this);
View
2 cubism.min.js
@@ -1 +1 @@
-(function(a){function c(a){var b={};return b.metric=function(b,c){function n(){var e=b.stop();g||(g=i),h=Math.round((b.start()-i)/j),a(c,g,e,j,function(a,b){if(a)return console.warn(a);b.forEach(function(a){l[Math.round((a[0]-i)/j)%k]=a[1]}),g=new Date(e-d*j)})}var f=new e,g,h,i=b.start(),j=b.step(),k=b.size(),l=new Array(k),m;setTimeout(n,10);var o=b.delay()-b.step()/2;return o>1e3&&(m=setTimeout(n,o)),b.on("change",function(){m&&clearTimeout(m),m=setTimeout(n,b.step()/2)}),b.on("cancel",function(){m=clearTimeout(m)}),f.size=function(){return k},f.extent=function(){return d3.extent(l)},f.valueAt=function(a){return l[(a+h)%k]},f.toString=function(){return c},f},b}function e(){}function f(a){return Math.floor(a/1e3)}function g(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(",").map(function(a,b){return[new Date(g+b*h),+a]}).slice(1)}function h(){}function i(a,b){if(a.size()!==b.size())throw new Error("different size!");var c=new e;return c.extent=function(){return d3.extent(d3.range(a.size()),c.valueAt)},c.valueAt=function(c){return a.valueAt(c)+b.valueAt(c)},c.toString=function(){return a+" + "+b},c.size=a.size,c}function j(a,b){if(a.size()!==b.size())throw new Error("different size!");var c=new e;return c.extent=function(){return d3.extent(d3.range(a.size()),c.valueAt)},c.valueAt=function(c){return a.valueAt(c)-b.valueAt(c)},c.toString=function(){return a+" - "+b},c.size=a.size,c}function k(a,b){if(a.size()!==b.size())throw new Error("different size!");var c=new e;return c.extent=function(){return d3.extent(d3.range(a.size()),c.valueAt)},c.valueAt=function(c){return a.valueAt(c)*b.valueAt(c)},c.toString=function(){return a+" * "+b},c.size=a.size,c}function l(a,b){if(a.size()!==b.size())throw new Error("different size!");var c=new e;return c.extent=function(){return d3.extent(d3.range(a.size()),c.valueAt)},c.valueAt=function(c){return a.valueAt(c)/b.valueAt(c)},c.toString=function(){return a+" / "+b},c.size=a.size,c}function m(a,b){b=+b,a=+a;var c=new e;return c.extent=function(){return[b,b]},c.valueAt=function(){return b},c.toString=function(){return b+""},c.size=function(){return a},c}var b=a.cubism={version:"0.0.1"},d=6;b.cube=function(a){arguments.length||(a="");var b=d3.time.format.iso,d=c(function(c,d,e,f,g){d3.json(a+"/1.0/metric"+"?expression="+encodeURIComponent(c)+"&start="+b(d)+"&stop="+b(e)+"&step="+f,function(a){if(!a)return g(new Error("unable to load data"));g(null,a.map(function(a){return[b.parse(a.time),a.value]}))})});return d.toString=function(){return a},d},b.graphite=function(a){arguments.length||(a="");var b=c(function(b,c,d,e,h){d3.text(a+"/render?format=raw"+"&target="+encodeURIComponent("alias("+b+",'')")+"&from="+f(c-2*e)+"&until="+f(d-1e3),function(a){if(!a)return h(new Error("unable to load data"));h(null,g(a))})});return b.toString=function(){return a},b},b.context=function(){function i(){k(),f.change.call(a),j()}function j(){g=setTimeout(i,a.delay())}function k(){var f=Date.now();return c=new Date(Math.floor(f/d)*d),b=new Date(c-e*d),a}var a=new h,b=new Date(NaN),c=new Date(NaN),d,e,f=d3.dispatch("change","cancel"),g;return setTimeout(j,10),a.start=function(){return b},a.stop=function(){return c},a.delay=function(){return+c+d-Date.now()},a.step=function(a){if(!arguments.length)return d;if(g)throw new Error("step cannot be changed mid-flight");return d=+a,k()},a.size=function(a){if(!arguments.length)return e;if(g)throw new Error("size cannot be changed mid-flight");return e=+a,k()},a.shift=function(g){var i=new h;return i.start=function(){return new Date(+b+g)},i.stop=function(){return new Date(+c+g)},i.delay=a.delay,i.step=function(){return d},i.size=function(){return e},i.timeAt=function(a){return new Date(+b+a*d+g)},d3.rebind(i,f,"on")},a.cancel=function(){return g&&(g=clearTimeout(g),f.cancel.call(a)),a},a.timeAt=function(a){return new Date(+b+a*d)},d3.rebind(a,f,"on"),a.step(1e4).size(1440)},e.prototype.add=function(a){return i(this,a instanceof e?a:m(this.size(),a))},e.prototype.subtract=function(a){return j(this,a instanceof e?a:m(this.size(),a))},e.prototype.multiply=function(a){return k(this,a instanceof e?a:m(this.size(),a))},e.prototype.divide=function(a){return l(this,a instanceof e?a:m(this.size(),a))},h.prototype.constant=function(a){return m(this.size(),a)}})(this);
+(function(a){function c(a){return a}function d(a){var b={};return b.metric=function(b,c){function p(){var d=b.stop();i||(i=k),j=Math.round((b.start()-k)/l),a(c,i,d,l,function(a,b){if(a)return console.warn(a);b.forEach(function(a){n[Math.round((a[0]-k)/l)%m]=a[1]}),i=new Date(d-e*l)})}var d=new f,h=++g,i,j,k=b.start(),l=b.step(),m=b.size(),n=new Array(m),o;setTimeout(p,10);var q=b.delay()-b.step()/2;return q>1e3&&(o=setTimeout(p,q)),b.on("change.metric-"+h,function(){o&&clearTimeout(o),o=setTimeout(p,b.step()/2)}),b.on("cancel.metric-"+h,function(){o=clearTimeout(o)}),d.size=function(){return m},d.extent=function(){return d3.extent(n)},d.valueAt=function(a){return n[(a+j)%m]},d.toString=function(){return c},d},b}function f(){}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(",").map(function(a,b){return[new Date(g+b*h),+a]}).slice(1)}function j(){}function k(a,b){if(a.size()!==b.size())throw new Error("different size!");var c=new f;return c.extent=function(){return d3.extent(d3.range(a.size()),c.valueAt)},c.valueAt=function(c){return a.valueAt(c)+b.valueAt(c)},c.toString=function(){return a+" + "+b},c.size=a.size,c}function l(a,b){if(a.size()!==b.size())throw new Error("different size!");var c=new f;return c.extent=function(){return d3.extent(d3.range(a.size()),c.valueAt)},c.valueAt=function(c){return a.valueAt(c)-b.valueAt(c)},c.toString=function(){return a+" - "+b},c.size=a.size,c}function m(a,b){if(a.size()!==b.size())throw new Error("different size!");var c=new f;return c.extent=function(){return d3.extent(d3.range(a.size()),c.valueAt)},c.valueAt=function(c){return a.valueAt(c)*b.valueAt(c)},c.toString=function(){return a+" * "+b},c.size=a.size,c}function n(a,b){if(a.size()!==b.size())throw new Error("different size!");var c=new f;return c.extent=function(){return d3.extent(d3.range(a.size()),c.valueAt)},c.valueAt=function(c){return a.valueAt(c)/b.valueAt(c)},c.toString=function(){return a+" / "+b},c.size=a.size,c}function o(a,b){b=+b,a=+a;var c=new f;return c.extent=function(){return[b,b]},c.valueAt=function(){return b},c.toString=function(){return b+""},c.size=function(){return a},c}var b=a.cubism={version:"0.0.1"},e=6,g=0;b.cube=function(a){arguments.length||(a="");var b=d3.time.format.iso,c=d(function(c,d,e,f,g){d3.json(a+"/1.0/metric"+"?expression="+encodeURIComponent(c)+"&start="+b(d)+"&stop="+b(e)+"&step="+f,function(a){if(!a)return g(new Error("unable to load data"));g(null,a.map(function(a){return[b.parse(a.time),a.value]}))})});return c.toString=function(){return a},c},b.graphite=function(a){arguments.length||(a="");var b=d(function(b,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))})});return b.toString=function(){return a},b},b.context=function(){function h(){k(),f.change.call(a),i()}function i(){g=setTimeout(h,a.delay())}function k(){var f=Date.now();return c=new Date(Math.floor(f/d)*d),b=new Date(c-e*d),a}var a=new j,b=new Date(NaN),c=new Date(NaN),d,e,f=d3.dispatch("change","cancel"),g;return setTimeout(i,10),a.start=function(){return b},a.stop=function(){return c},a.delay=function(){return+c+d-Date.now()},a.step=function(a){if(!arguments.length)return d;if(g)throw new Error("step cannot be changed mid-flight");return d=+a,k()},a.size=function(a){if(!arguments.length)return e;if(g)throw new Error("size cannot be changed mid-flight");return e=+a,k()},a.shift=function(g){var h=new j;return h.start=function(){return new Date(+b+g)},h.stop=function(){return new Date(+c+g)},h.delay=a.delay,h.step=function(){return d},h.size=function(){return e},h.timeAt=function(a){return new Date(+b+a*d+g)},d3.rebind(h,f,"on")},a.cancel=function(){return g&&(g=clearTimeout(g),f.cancel.call(a)),a},a.timeAt=function(a){return new Date(+b+a*d)},d3.rebind(a,f,"on"),a.step(1e4).size(1440)},f.prototype.add=function(a){return k(this,a instanceof f?a:o(this.size(),a))},f.prototype.subtract=function(a){return l(this,a instanceof f?a:o(this.size(),a))},f.prototype.multiply=function(a){return m(this,a instanceof f?a:o(this.size(),a))},f.prototype.divide=function(a){return n(this,a instanceof f?a:o(this.size(),a))},j.prototype.constant=function(a){return o(this.size(),a)},j.prototype.horizon=function(){function h(c){c.each(function(c,h){var i=d3.select(this),j=i.select("canvas"),k=i.select(".title"),l=i.select(".value"),m=typeof e=="function"?e.call(this,c,h):e,n=typeof f=="function"?f.call(this,c,h):f;j.empty()&&(j=i.append("canvas").attr("width",b).attr("height",d),k=i.append("span").attr("class","title"),l=i.append("span").attr("class","value"));var o=d3.scale.linear().domain([0,Math.max(-m.extent()[0],m.extent()[1])]).rangeRound([d,0]),p=j.node().getContext("2d");p.clearRect(0,0,b,d),p.fillStyle="steelblue";for(var h=0,q=b,r;h<q;++h){var r=m.valueAt(h);if(r<=0)continue;p.fillRect(h,r=o(r),1,d-r)}p.fillStyle="brown";if(a=="offset")for(var h=0,q=b,r;h<q;++h){var r=m.valueAt(h);if(r>=0)continue;p.fillRect(h,0,1,d-o(-r))}else for(var h=0,q=b,r;h<q;++h){var r=m.valueAt(h);if(r>=0)continue;p.fillRect(h,r=o(-r),1,d-r)}k.text(n),l.datum(r).text(isNaN(r)?null:g)})}var a="offset",b=this.size(),d=40,e=c,f=c,g=d3.format(".2s");return h.mode=function(b){return arguments.length?(a=b+"",h):a},h.height=function(a){return arguments.length?(d=+a,h):d},h.metric=function(a){return arguments.length?(e=a,h):e},h.title=function(a){return arguments.length?(f=a,h):f},h.format=function(a){return arguments.length?(g=a,h):g},h}})(this);
View
94 src/horizon.js
@@ -0,0 +1,94 @@
+cubism_context.prototype.horizon = function() {
+ var mode = "offset",
+ width = this.size(),
+ height = 40,
+ metric = cubism_identity,
+ title = cubism_identity,
+ format = d3.format(".2s");
+
+ // TODO configurable extent
+ // TODO configurable positive colors
+ // TODO configurable negative colors
+ // TODO configurable bands
+
+ function horizon(selection) {
+ selection.each(function(d, i) {
+ var div = d3.select(this),
+ canvas = div.select("canvas"),
+ tspan = div.select(".title"),
+ vspan = div.select(".value"),
+ metric_ = typeof metric === "function" ? metric.call(this, d, i) : metric,
+ title_ = typeof title === "function" ? title.call(this, d, i) : title;
+
+ if (canvas.empty()) {
+ canvas = div.append("canvas").attr("width", width).attr("height", height);
+ tspan = div.append("span").attr("class", "title");
+ vspan = div.append("span").attr("class", "value");
+ }
+
+ var y = d3.scale.linear()
+ .domain([0, Math.max(-metric_.extent()[0], metric_.extent()[1])])
+ .rangeRound([height, 0]);
+
+ var context = canvas.node().getContext("2d");
+ context.clearRect(0, 0, width, height);
+
+ context.fillStyle = "steelblue";
+ for (var i = 0, n = width, v; i < n; ++i) {
+ var v = metric_.valueAt(i);
+ if (v <= 0) continue;
+ context.fillRect(i, v = y(v), 1, height - v);
+ }
+
+ context.fillStyle = "brown";
+ if (mode == "offset") {
+ for (var i = 0, n = width, v; i < n; ++i) {
+ var v = metric_.valueAt(i);
+ if (v >= 0) continue;
+ context.fillRect(i, 0, 1, height - y(-v));
+ }
+ } else {
+ for (var i = 0, n = width, v; i < n; ++i) {
+ var v = metric_.valueAt(i);
+ if (v >= 0) continue;
+ context.fillRect(i, v = y(-v), 1, height - v);
+ }
+ }
+
+ tspan.text(title_);
+ vspan.datum(v).text(isNaN(v) ? null : format);
+ });
+ }
+
+ horizon.mode = function(_) {
+ if (!arguments.length) return mode;
+ mode = _ + "";
+ return horizon;
+ };
+
+ horizon.height = function(_) {
+ if (!arguments.length) return height;
+ height = +_;
+ return horizon;
+ };
+
+ horizon.metric = function(_) {
+ if (!arguments.length) return metric;
+ metric = _;
+ return horizon;
+ };
+
+ horizon.title = function(_) {
+ if (!arguments.length) return title;
+ title = _;
+ return horizon;
+ };
+
+ horizon.format = function(_) {
+ if (!arguments.length) return format;
+ format = _;
+ return horizon;
+ };
+
+ return horizon;
+};
View
1 src/identity.js
@@ -0,0 +1 @@
+function cubism_identity(d) { return d; }
View
2 src/metric.js
@@ -1 +1,3 @@
function cubism_metric() {}
+
+var cubism_metricId = 0;
View
5 src/source.js
@@ -3,6 +3,7 @@ function cubism_source(request) {
source.metric = function(context, expression) {
var metric = new cubism_metric,
+ id = ++cubism_metricId,
last,
offset,
offsetTime = context.start(),
@@ -32,13 +33,13 @@ function cubism_source(request) {
if (delay > 1000) timeout = setTimeout(refresh, delay);
// When the context changes, delay the request for a half-interval.
- context.on("change", function() {
+ context.on("change.metric-" + id, function() {
if (timeout) clearTimeout(timeout);
timeout = setTimeout(refresh, context.step() / 2);
});
// When the context is closed, cancel any pending refresh.
- context.on("cancel", function() {
+ context.on("cancel.metric-" + id, function() {
timeout = clearTimeout(timeout);
});

0 comments on commit 3052036

Please sign in to comment.
Something went wrong with that request. Please try again.