wiki 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +17 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE.txt +22 -0
  5. data/README.md +29 -0
  6. data/Rakefile +1 -0
  7. data/lib/wiki.rb +2 -0
  8. data/lib/wiki/ReadMe.md +89 -0
  9. data/lib/wiki/config.ru +2 -0
  10. data/lib/wiki/favicon.rb +31 -0
  11. data/lib/wiki/page.rb +74 -0
  12. data/lib/wiki/random_id.rb +5 -0
  13. data/lib/wiki/server.rb +336 -0
  14. data/lib/wiki/server_helpers.rb +66 -0
  15. data/lib/wiki/stores/ReadMe.md +26 -0
  16. data/lib/wiki/stores/all.rb +3 -0
  17. data/lib/wiki/stores/couch.rb +121 -0
  18. data/lib/wiki/stores/file.rb +53 -0
  19. data/lib/wiki/stores/store.rb +38 -0
  20. data/lib/wiki/version.rb +3 -0
  21. data/lib/wiki/views/client/Gruntfile.js +50 -0
  22. data/lib/wiki/views/client/ReadMe.md +67 -0
  23. data/lib/wiki/views/client/build-test.bat +10 -0
  24. data/lib/wiki/views/client/build.bat +8 -0
  25. data/lib/wiki/views/client/builder.pl +41 -0
  26. data/lib/wiki/views/client/client.coffee +3 -0
  27. data/lib/wiki/views/client/client.js +3607 -0
  28. data/lib/wiki/views/client/crosses.png +0 -0
  29. data/lib/wiki/views/client/images/external-link-ltr-icon.png +0 -0
  30. data/lib/wiki/views/client/images/noise.png +0 -0
  31. data/lib/wiki/views/client/images/oops.jpg +0 -0
  32. data/lib/wiki/views/client/js/d3/d3.behavior.js +198 -0
  33. data/lib/wiki/views/client/js/d3/d3.chart.js +984 -0
  34. data/lib/wiki/views/client/js/d3/d3.csv.js +92 -0
  35. data/lib/wiki/views/client/js/d3/d3.geo.js +566 -0
  36. data/lib/wiki/views/client/js/d3/d3.geom.js +825 -0
  37. data/lib/wiki/views/client/js/d3/d3.js +3597 -0
  38. data/lib/wiki/views/client/js/d3/d3.layout.js +1923 -0
  39. data/lib/wiki/views/client/js/d3/d3.time.js +660 -0
  40. data/lib/wiki/views/client/js/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
  41. data/lib/wiki/views/client/js/images/ui-icons_222222_256x240.png +0 -0
  42. data/lib/wiki/views/client/js/jquery-1.6.2.min.js +18 -0
  43. data/lib/wiki/views/client/js/jquery-1.7.1.min.js +4 -0
  44. data/lib/wiki/views/client/js/jquery-1.9.1.min.js +5 -0
  45. data/lib/wiki/views/client/js/jquery-migrate-1.1.1.min.js +3 -0
  46. data/lib/wiki/views/client/js/jquery-ui-1.10.1.custom.min.css +5 -0
  47. data/lib/wiki/views/client/js/jquery-ui-1.10.1.custom.min.js +6 -0
  48. data/lib/wiki/views/client/js/jquery-ui-1.8.16.custom.css +339 -0
  49. data/lib/wiki/views/client/js/jquery-ui-1.8.16.custom.min.js +315 -0
  50. data/lib/wiki/views/client/js/jquery.ie.cors.js +310 -0
  51. data/lib/wiki/views/client/js/jquery.ui.touch-punch.min.js +11 -0
  52. data/lib/wiki/views/client/js/modernizr.custom.63710.js +824 -0
  53. data/lib/wiki/views/client/js/sockjs-0.3.min.js +27 -0
  54. data/lib/wiki/views/client/js/underscore-min.js +30 -0
  55. data/lib/wiki/views/client/mkplugin.sh +97 -0
  56. data/lib/wiki/views/client/package.json +36 -0
  57. data/lib/wiki/views/client/runtests.html +26 -0
  58. data/lib/wiki/views/client/style.css +339 -0
  59. data/lib/wiki/views/client/test/mocha.css +231 -0
  60. data/lib/wiki/views/client/test/mocha.js +5340 -0
  61. data/lib/wiki/views/client/test/testclient.js +17133 -0
  62. data/lib/wiki/views/client/testclient.coffee +18 -0
  63. data/lib/wiki/views/client/theme/granite.css +59 -0
  64. data/lib/wiki/views/client/theme/stoneSeamless.jpg +0 -0
  65. data/lib/wiki/views/client/twitter-maintainance.jpg +0 -0
  66. data/lib/wiki/views/layout.haml +56 -0
  67. data/lib/wiki/views/oops.haml +5 -0
  68. data/lib/wiki/views/page.haml +20 -0
  69. data/lib/wiki/views/static.html +30 -0
  70. data/lib/wiki/views/view.haml +2 -0
  71. data/wiki.gemspec +28 -0
  72. metadata +121 -0
@@ -0,0 +1,198 @@
1
+ (function(){d3.behavior = {};
2
+ // TODO unbind zoom behavior?
3
+ // TODO unbind listener?
4
+ d3.behavior.zoom = function() {
5
+ var xyz = [0, 0, 0],
6
+ event = d3.dispatch("zoom");
7
+
8
+ function zoom() {
9
+ this
10
+ .on("mousedown.zoom", mousedown)
11
+ .on("mousewheel.zoom", mousewheel)
12
+ .on("DOMMouseScroll.zoom", dblclick)
13
+ .on("dblclick.zoom", dblclick)
14
+ .on("touchstart.zoom", touchstart);
15
+
16
+ d3.select(window)
17
+ .on("mousemove.zoom", d3_behavior_zoomMousemove)
18
+ .on("mouseup.zoom", d3_behavior_zoomMouseup)
19
+ .on("touchmove.zoom", d3_behavior_zoomTouchmove)
20
+ .on("touchend.zoom", d3_behavior_zoomTouchup);
21
+ }
22
+
23
+ // snapshot the local context for subsequent dispatch
24
+ function start() {
25
+ d3_behavior_zoomXyz = xyz;
26
+ d3_behavior_zoomDispatch = event.zoom.dispatch;
27
+ d3_behavior_zoomTarget = this;
28
+ d3_behavior_zoomArguments = arguments;
29
+ }
30
+
31
+ function mousedown() {
32
+ start.apply(this, arguments);
33
+ d3_behavior_zoomPanning = d3_behavior_zoomLocation(d3.svg.mouse(d3_behavior_zoomTarget));
34
+ d3.event.preventDefault();
35
+ window.focus();
36
+ }
37
+
38
+ // store starting mouse location
39
+ function mousewheel() {
40
+ start.apply(this, arguments);
41
+ if (!d3_behavior_zoomZooming) d3_behavior_zoomZooming = d3_behavior_zoomLocation(d3.svg.mouse(d3_behavior_zoomTarget));
42
+ d3_behavior_zoomTo(d3_behavior_zoomDelta() + xyz[2], d3.svg.mouse(d3_behavior_zoomTarget), d3_behavior_zoomZooming);
43
+ }
44
+
45
+ function dblclick() {
46
+ start.apply(this, arguments);
47
+ var mouse = d3.svg.mouse(d3_behavior_zoomTarget);
48
+ d3_behavior_zoomTo(d3.event.shiftKey ? Math.ceil(xyz[2] - 1) : Math.floor(xyz[2] + 1), mouse, d3_behavior_zoomLocation(mouse));
49
+ }
50
+
51
+ // doubletap detection
52
+ function touchstart() {
53
+ start.apply(this, arguments);
54
+ var touches = d3_behavior_zoomTouchup(),
55
+ touch,
56
+ now = Date.now();
57
+ if ((touches.length === 1) && (now - d3_behavior_zoomLast < 300)) {
58
+ d3_behavior_zoomTo(1 + Math.floor(xyz[2]), touch = touches[0], d3_behavior_zoomLocations[touch.identifier]);
59
+ }
60
+ d3_behavior_zoomLast = now;
61
+ }
62
+
63
+ zoom.on = function(type, listener) {
64
+ event[type].add(listener);
65
+ return zoom;
66
+ };
67
+
68
+ return zoom;
69
+ };
70
+
71
+ var d3_behavior_zoomDiv,
72
+ d3_behavior_zoomPanning,
73
+ d3_behavior_zoomZooming,
74
+ d3_behavior_zoomLocations = {}, // identifier -> location
75
+ d3_behavior_zoomLast = 0,
76
+ d3_behavior_zoomXyz,
77
+ d3_behavior_zoomDispatch,
78
+ d3_behavior_zoomTarget,
79
+ d3_behavior_zoomArguments;
80
+
81
+ function d3_behavior_zoomLocation(point) {
82
+ return [
83
+ point[0] - d3_behavior_zoomXyz[0],
84
+ point[1] - d3_behavior_zoomXyz[1],
85
+ d3_behavior_zoomXyz[2]
86
+ ];
87
+ }
88
+
89
+ // detect the pixels that would be scrolled by this wheel event
90
+ function d3_behavior_zoomDelta() {
91
+
92
+ // mousewheel events are totally broken!
93
+ // https://bugs.webkit.org/show_bug.cgi?id=40441
94
+ // not only that, but Chrome and Safari differ in re. to acceleration!
95
+ if (!d3_behavior_zoomDiv) {
96
+ d3_behavior_zoomDiv = d3.select("body").append("div")
97
+ .style("visibility", "hidden")
98
+ .style("top", 0)
99
+ .style("height", 0)
100
+ .style("width", 0)
101
+ .style("overflow-y", "scroll")
102
+ .append("div")
103
+ .style("height", "2000px")
104
+ .node().parentNode;
105
+ }
106
+
107
+ var e = d3.event, delta;
108
+ try {
109
+ d3_behavior_zoomDiv.scrollTop = 1000;
110
+ d3_behavior_zoomDiv.dispatchEvent(e);
111
+ delta = 1000 - d3_behavior_zoomDiv.scrollTop;
112
+ } catch (error) {
113
+ delta = e.wheelDelta || -e.detail;
114
+ }
115
+
116
+ return delta * .005;
117
+ }
118
+
119
+ // Note: Since we don't rotate, it's possible for the touches to become
120
+ // slightly detached from their original positions. Thus, we recompute the
121
+ // touch points on touchend as well as touchstart!
122
+ function d3_behavior_zoomTouchup() {
123
+ var touches = d3.svg.touches(d3_behavior_zoomTarget),
124
+ i = -1,
125
+ n = touches.length,
126
+ touch;
127
+ while (++i < n) d3_behavior_zoomLocations[(touch = touches[i]).identifier] = d3_behavior_zoomLocation(touch);
128
+ return touches;
129
+ }
130
+
131
+ function d3_behavior_zoomTouchmove() {
132
+ var touches = d3.svg.touches(d3_behavior_zoomTarget);
133
+ switch (touches.length) {
134
+
135
+ // single-touch pan
136
+ case 1: {
137
+ var touch = touches[0];
138
+ d3_behavior_zoomTo(d3_behavior_zoomXyz[2], touch, d3_behavior_zoomLocations[touch.identifier]);
139
+ break;
140
+ }
141
+
142
+ // double-touch pan + zoom
143
+ case 2: {
144
+ var p0 = touches[0],
145
+ p1 = touches[1],
146
+ p2 = [(p0[0] + p1[0]) / 2, (p0[1] + p1[1]) / 2],
147
+ l0 = d3_behavior_zoomLocations[p0.identifier],
148
+ l1 = d3_behavior_zoomLocations[p1.identifier],
149
+ l2 = [(l0[0] + l1[0]) / 2, (l0[1] + l1[1]) / 2, l0[2]];
150
+ d3_behavior_zoomTo(Math.log(d3.event.scale) / Math.LN2 + l0[2], p2, l2);
151
+ break;
152
+ }
153
+ }
154
+ }
155
+
156
+ function d3_behavior_zoomMousemove() {
157
+ d3_behavior_zoomZooming = null;
158
+ if (d3_behavior_zoomPanning) d3_behavior_zoomTo(d3_behavior_zoomXyz[2], d3.svg.mouse(d3_behavior_zoomTarget), d3_behavior_zoomPanning);
159
+ }
160
+
161
+ function d3_behavior_zoomMouseup() {
162
+ if (d3_behavior_zoomPanning) {
163
+ d3_behavior_zoomMousemove();
164
+ d3_behavior_zoomPanning = null;
165
+ }
166
+ }
167
+
168
+ function d3_behavior_zoomTo(z, x0, x1) {
169
+ var K = Math.pow(2, (d3_behavior_zoomXyz[2] = z) - x1[2]),
170
+ x = d3_behavior_zoomXyz[0] = x0[0] - K * x1[0],
171
+ y = d3_behavior_zoomXyz[1] = x0[1] - K * x1[1],
172
+ o = d3.event, // Events can be reentrant (e.g., focus).
173
+ k = Math.pow(2, z);
174
+
175
+ d3.event = {
176
+ scale: k,
177
+ translate: [x, y],
178
+ transform: function(sx, sy) {
179
+ if (sx) transform(sx, x);
180
+ if (sy) transform(sy, y);
181
+ }
182
+ };
183
+
184
+ function transform(scale, o) {
185
+ var domain = scale.__domain || (scale.__domain = scale.domain()),
186
+ range = scale.range().map(function(v) { return (v - o) / k; });
187
+ scale.domain(domain).domain(range.map(scale.invert));
188
+ }
189
+
190
+ try {
191
+ d3_behavior_zoomDispatch.apply(d3_behavior_zoomTarget, d3_behavior_zoomArguments);
192
+ } finally {
193
+ d3.event = o;
194
+ }
195
+
196
+ o.preventDefault();
197
+ }
198
+ })();
@@ -0,0 +1,984 @@
1
+ (function(){d3.chart = {};
2
+ // Inspired by http://informationandvisualization.de/blog/box-plot
3
+ d3.chart.box = function() {
4
+ var width = 1,
5
+ height = 1,
6
+ duration = 0,
7
+ domain = null,
8
+ value = Number,
9
+ whiskers = d3_chart_boxWhiskers,
10
+ quartiles = d3_chart_boxQuartiles,
11
+ tickFormat = null;
12
+
13
+ // For each small multiple…
14
+ function box(g) {
15
+ g.each(function(d, i) {
16
+ d = d.map(value).sort(d3.ascending);
17
+ var g = d3.select(this),
18
+ n = d.length,
19
+ min = d[0],
20
+ max = d[n - 1];
21
+
22
+ // Compute quartiles. Must return exactly 3 elements.
23
+ var quartileData = d.quartiles = quartiles(d);
24
+
25
+ // Compute whiskers. Must return exactly 2 elements, or null.
26
+ var whiskerIndices = whiskers && whiskers.call(this, d, i),
27
+ whiskerData = whiskerIndices && whiskerIndices.map(function(i) { return d[i]; });
28
+
29
+ // Compute outliers. If no whiskers are specified, all data are "outliers".
30
+ // We compute the outliers as indices, so that we can join across transitions!
31
+ var outlierIndices = whiskerIndices
32
+ ? d3.range(0, whiskerIndices[0]).concat(d3.range(whiskerIndices[1] + 1, n))
33
+ : d3.range(n);
34
+
35
+ // Compute the new x-scale.
36
+ var x1 = d3.scale.linear()
37
+ .domain(domain && domain.call(this, d, i) || [min, max])
38
+ .range([height, 0]);
39
+
40
+ // Retrieve the old x-scale, if this is an update.
41
+ var x0 = this.__chart__ || d3.scale.linear()
42
+ .domain([0, Infinity])
43
+ .range(x1.range());
44
+
45
+ // Stash the new scale.
46
+ this.__chart__ = x1;
47
+
48
+ // Note: the box, median, and box tick elements are fixed in number,
49
+ // so we only have to handle enter and update. In contrast, the outliers
50
+ // and other elements are variable, so we need to exit them! Variable
51
+ // elements also fade in and out.
52
+
53
+ // Update center line: the vertical line spanning the whiskers.
54
+ var center = g.selectAll("line.center")
55
+ .data(whiskerData ? [whiskerData] : []);
56
+
57
+ center.enter().insert("svg:line", "rect")
58
+ .attr("class", "center")
59
+ .attr("x1", width / 2)
60
+ .attr("y1", function(d) { return x0(d[0]); })
61
+ .attr("x2", width / 2)
62
+ .attr("y2", function(d) { return x0(d[1]); })
63
+ .style("opacity", 1e-6)
64
+ .transition()
65
+ .duration(duration)
66
+ .style("opacity", 1)
67
+ .attr("y1", function(d) { return x1(d[0]); })
68
+ .attr("y2", function(d) { return x1(d[1]); });
69
+
70
+ center.transition()
71
+ .duration(duration)
72
+ .style("opacity", 1)
73
+ .attr("y1", function(d) { return x1(d[0]); })
74
+ .attr("y2", function(d) { return x1(d[1]); });
75
+
76
+ center.exit().transition()
77
+ .duration(duration)
78
+ .style("opacity", 1e-6)
79
+ .attr("y1", function(d) { return x1(d[0]); })
80
+ .attr("y2", function(d) { return x1(d[1]); })
81
+ .remove();
82
+
83
+ // Update innerquartile box.
84
+ var box = g.selectAll("rect.box")
85
+ .data([quartileData]);
86
+
87
+ box.enter().append("svg:rect")
88
+ .attr("class", "box")
89
+ .attr("x", 0)
90
+ .attr("y", function(d) { return x0(d[2]); })
91
+ .attr("width", width)
92
+ .attr("height", function(d) { return x0(d[0]) - x0(d[2]); })
93
+ .transition()
94
+ .duration(duration)
95
+ .attr("y", function(d) { return x1(d[2]); })
96
+ .attr("height", function(d) { return x1(d[0]) - x1(d[2]); });
97
+
98
+ box.transition()
99
+ .duration(duration)
100
+ .attr("y", function(d) { return x1(d[2]); })
101
+ .attr("height", function(d) { return x1(d[0]) - x1(d[2]); });
102
+
103
+ // Update median line.
104
+ var medianLine = g.selectAll("line.median")
105
+ .data([quartileData[1]]);
106
+
107
+ medianLine.enter().append("svg:line")
108
+ .attr("class", "median")
109
+ .attr("x1", 0)
110
+ .attr("y1", x0)
111
+ .attr("x2", width)
112
+ .attr("y2", x0)
113
+ .transition()
114
+ .duration(duration)
115
+ .attr("y1", x1)
116
+ .attr("y2", x1);
117
+
118
+ medianLine.transition()
119
+ .duration(duration)
120
+ .attr("y1", x1)
121
+ .attr("y2", x1);
122
+
123
+ // Update whiskers.
124
+ var whisker = g.selectAll("line.whisker")
125
+ .data(whiskerData || []);
126
+
127
+ whisker.enter().insert("svg:line", "circle, text")
128
+ .attr("class", "whisker")
129
+ .attr("x1", 0)
130
+ .attr("y1", x0)
131
+ .attr("x2", width)
132
+ .attr("y2", x0)
133
+ .style("opacity", 1e-6)
134
+ .transition()
135
+ .duration(duration)
136
+ .attr("y1", x1)
137
+ .attr("y2", x1)
138
+ .style("opacity", 1);
139
+
140
+ whisker.transition()
141
+ .duration(duration)
142
+ .attr("y1", x1)
143
+ .attr("y2", x1)
144
+ .style("opacity", 1);
145
+
146
+ whisker.exit().transition()
147
+ .duration(duration)
148
+ .attr("y1", x1)
149
+ .attr("y2", x1)
150
+ .style("opacity", 1e-6)
151
+ .remove();
152
+
153
+ // Update outliers.
154
+ var outlier = g.selectAll("circle.outlier")
155
+ .data(outlierIndices, Number);
156
+
157
+ outlier.enter().insert("svg:circle", "text")
158
+ .attr("class", "outlier")
159
+ .attr("r", 5)
160
+ .attr("cx", width / 2)
161
+ .attr("cy", function(i) { return x0(d[i]); })
162
+ .style("opacity", 1e-6)
163
+ .transition()
164
+ .duration(duration)
165
+ .attr("cy", function(i) { return x1(d[i]); })
166
+ .style("opacity", 1);
167
+
168
+ outlier.transition()
169
+ .duration(duration)
170
+ .attr("cy", function(i) { return x1(d[i]); })
171
+ .style("opacity", 1);
172
+
173
+ outlier.exit().transition()
174
+ .duration(duration)
175
+ .attr("cy", function(i) { return x1(d[i]); })
176
+ .style("opacity", 1e-6)
177
+ .remove();
178
+
179
+ // Compute the tick format.
180
+ var format = tickFormat || x1.tickFormat(8);
181
+
182
+ // Update box ticks.
183
+ var boxTick = g.selectAll("text.box")
184
+ .data(quartileData);
185
+
186
+ boxTick.enter().append("svg:text")
187
+ .attr("class", "box")
188
+ .attr("dy", ".3em")
189
+ .attr("dx", function(d, i) { return i & 1 ? 6 : -6 })
190
+ .attr("x", function(d, i) { return i & 1 ? width : 0 })
191
+ .attr("y", x0)
192
+ .attr("text-anchor", function(d, i) { return i & 1 ? "start" : "end"; })
193
+ .text(format)
194
+ .transition()
195
+ .duration(duration)
196
+ .attr("y", x1);
197
+
198
+ boxTick.transition()
199
+ .duration(duration)
200
+ .text(format)
201
+ .attr("y", x1);
202
+
203
+ // Update whisker ticks. These are handled separately from the box
204
+ // ticks because they may or may not exist, and we want don't want
205
+ // to join box ticks pre-transition with whisker ticks post-.
206
+ var whiskerTick = g.selectAll("text.whisker")
207
+ .data(whiskerData || []);
208
+
209
+ whiskerTick.enter().append("svg:text")
210
+ .attr("class", "whisker")
211
+ .attr("dy", ".3em")
212
+ .attr("dx", 6)
213
+ .attr("x", width)
214
+ .attr("y", x0)
215
+ .text(format)
216
+ .style("opacity", 1e-6)
217
+ .transition()
218
+ .duration(duration)
219
+ .attr("y", x1)
220
+ .style("opacity", 1);
221
+
222
+ whiskerTick.transition()
223
+ .duration(duration)
224
+ .text(format)
225
+ .attr("y", x1)
226
+ .style("opacity", 1);
227
+
228
+ whiskerTick.exit().transition()
229
+ .duration(duration)
230
+ .attr("y", x1)
231
+ .style("opacity", 1e-6)
232
+ .remove();
233
+ });
234
+ d3.timer.flush();
235
+ }
236
+
237
+ box.width = function(x) {
238
+ if (!arguments.length) return width;
239
+ width = x;
240
+ return box;
241
+ };
242
+
243
+ box.height = function(x) {
244
+ if (!arguments.length) return height;
245
+ height = x;
246
+ return box;
247
+ };
248
+
249
+ box.tickFormat = function(x) {
250
+ if (!arguments.length) return tickFormat;
251
+ tickFormat = x;
252
+ return box;
253
+ };
254
+
255
+ box.duration = function(x) {
256
+ if (!arguments.length) return duration;
257
+ duration = x;
258
+ return box;
259
+ };
260
+
261
+ box.domain = function(x) {
262
+ if (!arguments.length) return domain;
263
+ domain = x == null ? x : d3.functor(x);
264
+ return box;
265
+ };
266
+
267
+ box.value = function(x) {
268
+ if (!arguments.length) return value;
269
+ value = x;
270
+ return box;
271
+ };
272
+
273
+ box.whiskers = function(x) {
274
+ if (!arguments.length) return whiskers;
275
+ whiskers = x;
276
+ return box;
277
+ };
278
+
279
+ box.quartiles = function(x) {
280
+ if (!arguments.length) return quartiles;
281
+ quartiles = x;
282
+ return box;
283
+ };
284
+
285
+ return box;
286
+ };
287
+
288
+ function d3_chart_boxWhiskers(d) {
289
+ return [0, d.length - 1];
290
+ }
291
+
292
+ function d3_chart_boxQuartiles(d) {
293
+ return [
294
+ d3.quantile(d, .25),
295
+ d3.quantile(d, .5),
296
+ d3.quantile(d, .75)
297
+ ];
298
+ }
299
+ // Chart design based on the recommendations of Stephen Few. Implementation
300
+ // based on the work of Clint Ivy, Jamie Love, and Jason Davies.
301
+ // http://projects.instantcognition.com/protovis/bulletchart/
302
+ d3.chart.bullet = function() {
303
+ var orient = "left", // TODO top & bottom
304
+ reverse = false,
305
+ duration = 0,
306
+ ranges = d3_chart_bulletRanges,
307
+ markers = d3_chart_bulletMarkers,
308
+ measures = d3_chart_bulletMeasures,
309
+ width = 380,
310
+ height = 30,
311
+ tickFormat = null;
312
+
313
+ // For each small multiple…
314
+ function bullet(g) {
315
+ g.each(function(d, i) {
316
+ var rangez = ranges.call(this, d, i).slice().sort(d3.descending),
317
+ markerz = markers.call(this, d, i).slice().sort(d3.descending),
318
+ measurez = measures.call(this, d, i).slice().sort(d3.descending),
319
+ g = d3.select(this);
320
+
321
+ // Compute the new x-scale.
322
+ var x1 = d3.scale.linear()
323
+ .domain([0, Math.max(rangez[0], markerz[0], measurez[0])])
324
+ .range(reverse ? [width, 0] : [0, width]);
325
+
326
+ // Retrieve the old x-scale, if this is an update.
327
+ var x0 = this.__chart__ || d3.scale.linear()
328
+ .domain([0, Infinity])
329
+ .range(x1.range());
330
+
331
+ // Stash the new scale.
332
+ this.__chart__ = x1;
333
+
334
+ // Derive width-scales from the x-scales.
335
+ var w0 = d3_chart_bulletWidth(x0),
336
+ w1 = d3_chart_bulletWidth(x1);
337
+
338
+ // Update the range rects.
339
+ var range = g.selectAll("rect.range")
340
+ .data(rangez);
341
+
342
+ range.enter().append("svg:rect")
343
+ .attr("class", function(d, i) { return "range s" + i; })
344
+ .attr("width", w0)
345
+ .attr("height", height)
346
+ .attr("x", reverse ? x0 : 0)
347
+ .transition()
348
+ .duration(duration)
349
+ .attr("width", w1)
350
+ .attr("x", reverse ? x1 : 0);
351
+
352
+ range.transition()
353
+ .duration(duration)
354
+ .attr("x", reverse ? x1 : 0)
355
+ .attr("width", w1)
356
+ .attr("height", height);
357
+
358
+ // Update the measure rects.
359
+ var measure = g.selectAll("rect.measure")
360
+ .data(measurez);
361
+
362
+ measure.enter().append("svg:rect")
363
+ .attr("class", function(d, i) { return "measure s" + i; })
364
+ .attr("width", w0)
365
+ .attr("height", height / 3)
366
+ .attr("x", reverse ? x0 : 0)
367
+ .attr("y", height / 3)
368
+ .transition()
369
+ .duration(duration)
370
+ .attr("width", w1)
371
+ .attr("x", reverse ? x1 : 0);
372
+
373
+ measure.transition()
374
+ .duration(duration)
375
+ .attr("width", w1)
376
+ .attr("height", height / 3)
377
+ .attr("x", reverse ? x1 : 0)
378
+ .attr("y", height / 3);
379
+
380
+ // Update the marker lines.
381
+ var marker = g.selectAll("line.marker")
382
+ .data(markerz);
383
+
384
+ marker.enter().append("svg:line")
385
+ .attr("class", "marker")
386
+ .attr("x1", x0)
387
+ .attr("x2", x0)
388
+ .attr("y1", height / 6)
389
+ .attr("y2", height * 5 / 6)
390
+ .transition()
391
+ .duration(duration)
392
+ .attr("x1", x1)
393
+ .attr("x2", x1);
394
+
395
+ marker.transition()
396
+ .duration(duration)
397
+ .attr("x1", x1)
398
+ .attr("x2", x1)
399
+ .attr("y1", height / 6)
400
+ .attr("y2", height * 5 / 6);
401
+
402
+ // Compute the tick format.
403
+ var format = tickFormat || x1.tickFormat(8);
404
+
405
+ // Update the tick groups.
406
+ var tick = g.selectAll("g.tick")
407
+ .data(x1.ticks(8), function(d) {
408
+ return this.textContent || format(d);
409
+ });
410
+
411
+ // Initialize the ticks with the old scale, x0.
412
+ var tickEnter = tick.enter().append("svg:g")
413
+ .attr("class", "tick")
414
+ .attr("transform", d3_chart_bulletTranslate(x0))
415
+ .style("opacity", 1e-6);
416
+
417
+ tickEnter.append("svg:line")
418
+ .attr("y1", height)
419
+ .attr("y2", height * 7 / 6);
420
+
421
+ tickEnter.append("svg:text")
422
+ .attr("text-anchor", "middle")
423
+ .attr("dy", "1em")
424
+ .attr("y", height * 7 / 6)
425
+ .text(format);
426
+
427
+ // Transition the entering ticks to the new scale, x1.
428
+ tickEnter.transition()
429
+ .duration(duration)
430
+ .attr("transform", d3_chart_bulletTranslate(x1))
431
+ .style("opacity", 1);
432
+
433
+ // Transition the updating ticks to the new scale, x1.
434
+ var tickUpdate = tick.transition()
435
+ .duration(duration)
436
+ .attr("transform", d3_chart_bulletTranslate(x1))
437
+ .style("opacity", 1);
438
+
439
+ tickUpdate.select("line")
440
+ .attr("y1", height)
441
+ .attr("y2", height * 7 / 6);
442
+
443
+ tickUpdate.select("text")
444
+ .attr("y", height * 7 / 6);
445
+
446
+ // Transition the exiting ticks to the new scale, x1.
447
+ tick.exit().transition()
448
+ .duration(duration)
449
+ .attr("transform", d3_chart_bulletTranslate(x1))
450
+ .style("opacity", 1e-6)
451
+ .remove();
452
+ });
453
+ d3.timer.flush();
454
+ }
455
+
456
+ // left, right, top, bottom
457
+ bullet.orient = function(x) {
458
+ if (!arguments.length) return orient;
459
+ orient = x;
460
+ reverse = orient == "right" || orient == "bottom";
461
+ return bullet;
462
+ };
463
+
464
+ // ranges (bad, satisfactory, good)
465
+ bullet.ranges = function(x) {
466
+ if (!arguments.length) return ranges;
467
+ ranges = x;
468
+ return bullet;
469
+ };
470
+
471
+ // markers (previous, goal)
472
+ bullet.markers = function(x) {
473
+ if (!arguments.length) return markers;
474
+ markers = x;
475
+ return bullet;
476
+ };
477
+
478
+ // measures (actual, forecast)
479
+ bullet.measures = function(x) {
480
+ if (!arguments.length) return measures;
481
+ measures = x;
482
+ return bullet;
483
+ };
484
+
485
+ bullet.width = function(x) {
486
+ if (!arguments.length) return width;
487
+ width = x;
488
+ return bullet;
489
+ };
490
+
491
+ bullet.height = function(x) {
492
+ if (!arguments.length) return height;
493
+ height = x;
494
+ return bullet;
495
+ };
496
+
497
+ bullet.tickFormat = function(x) {
498
+ if (!arguments.length) return tickFormat;
499
+ tickFormat = x;
500
+ return bullet;
501
+ };
502
+
503
+ bullet.duration = function(x) {
504
+ if (!arguments.length) return duration;
505
+ duration = x;
506
+ return bullet;
507
+ };
508
+
509
+ return bullet;
510
+ };
511
+
512
+ function d3_chart_bulletRanges(d) {
513
+ return d.ranges;
514
+ }
515
+
516
+ function d3_chart_bulletMarkers(d) {
517
+ return d.markers;
518
+ }
519
+
520
+ function d3_chart_bulletMeasures(d) {
521
+ return d.measures;
522
+ }
523
+
524
+ function d3_chart_bulletTranslate(x) {
525
+ return function(d) {
526
+ return "translate(" + x(d) + ",0)";
527
+ };
528
+ }
529
+
530
+ function d3_chart_bulletWidth(x) {
531
+ var x0 = x(0);
532
+ return function(d) {
533
+ return Math.abs(x(d) - x0);
534
+ };
535
+ }
536
+ // Implements a horizon layout, which is a variation of a single-series
537
+ // area chart where the area is folded into multiple bands. Color is used to
538
+ // encode band, allowing the size of the chart to be reduced significantly
539
+ // without impeding readability. This layout algorithm is based on the work of
540
+ // J. Heer, N. Kong and M. Agrawala in "Sizing the Horizon: The Effects of Chart
541
+ // Size and Layering on the Graphical Perception of Time Series Visualizations",
542
+ // CHI 2009. http://hci.stanford.edu/publications/2009/heer-horizon-chi09.pdf
543
+ d3.chart.horizon = function() {
544
+ var bands = 1, // between 1 and 5, typically
545
+ mode = "offset", // or mirror
546
+ interpolate = "linear", // or basis, monotone, step-before, etc.
547
+ x = d3_chart_horizonX,
548
+ y = d3_chart_horizonY,
549
+ w = 960,
550
+ h = 40,
551
+ duration = 0;
552
+
553
+ var color = d3.scale.linear()
554
+ .domain([-1, 0, 1])
555
+ .range(["#d62728", "#fff", "#1f77b4"]);
556
+
557
+ // For each small multiple…
558
+ function horizon(g) {
559
+ g.each(function(d, i) {
560
+ var g = d3.select(this),
561
+ n = 2 * bands + 1,
562
+ xMin = Infinity,
563
+ xMax = -Infinity,
564
+ yMax = -Infinity,
565
+ x0, // old x-scale
566
+ y0, // old y-scale
567
+ id; // unique id for paths
568
+
569
+ // Compute x- and y-values along with extents.
570
+ var data = d.map(function(d, i) {
571
+ var xv = x.call(this, d, i),
572
+ yv = y.call(this, d, i);
573
+ if (xv < xMin) xMin = xv;
574
+ if (xv > xMax) xMax = xv;
575
+ if (-yv > yMax) yMax = -yv;
576
+ if (yv > yMax) yMax = yv;
577
+ return [xv, yv];
578
+ });
579
+
580
+ // Compute the new x- and y-scales.
581
+ var x1 = d3.scale.linear().domain([xMin, xMax]).range([0, w]),
582
+ y1 = d3.scale.linear().domain([0, yMax]).range([0, h * bands]);
583
+
584
+ // Retrieve the old scales, if this is an update.
585
+ if (this.__chart__) {
586
+ x0 = this.__chart__.x;
587
+ y0 = this.__chart__.y;
588
+ id = this.__chart__.id;
589
+ } else {
590
+ x0 = d3.scale.linear().domain([0, Infinity]).range(x1.range());
591
+ y0 = d3.scale.linear().domain([0, Infinity]).range(y1.range());
592
+ id = ++d3_chart_horizonId;
593
+ }
594
+
595
+ // We'll use a defs to store the area path and the clip path.
596
+ var defs = g.selectAll("defs")
597
+ .data([data]);
598
+
599
+ var defsEnter = defs.enter().append("svg:defs");
600
+
601
+ // The clip path is a simple rect.
602
+ defsEnter.append("svg:clipPath")
603
+ .attr("id", "d3_chart_horizon_clip" + id)
604
+ .append("svg:rect")
605
+ .attr("width", w)
606
+ .attr("height", h);
607
+
608
+ defs.select("rect").transition()
609
+ .duration(duration)
610
+ .attr("width", w)
611
+ .attr("height", h);
612
+
613
+ // The area path is rendered with our resuable d3.svg.area.
614
+ defsEnter.append("svg:path")
615
+ .attr("id", "d3_chart_horizon_path" + id)
616
+ .attr("d", d3_chart_horizonArea
617
+ .interpolate(interpolate)
618
+ .x(function(d) { return x0(d[0]); })
619
+ .y0(h * bands)
620
+ .y1(function(d) { return h * bands - y0(d[1]); }))
621
+ .transition()
622
+ .duration(duration)
623
+ .attr("d", d3_chart_horizonArea
624
+ .x(function(d) { return x1(d[0]); })
625
+ .y1(function(d) { return h * bands - y1(d[1]); }));
626
+
627
+ defs.select("path").transition()
628
+ .duration(duration)
629
+ .attr("d", d3_chart_horizonArea);
630
+
631
+ // We'll use a container to clip all horizon layers at once.
632
+ g.selectAll("g")
633
+ .data([null])
634
+ .enter().append("svg:g")
635
+ .attr("clip-path", "url(#d3_chart_horizon_clip" + id + ")");
636
+
637
+ // Define the transform function based on the mode.
638
+ var transform = mode == "offset"
639
+ ? function(d) { return "translate(0," + (d + (d < 0) - bands) * h + ")"; }
640
+ : function(d) { return (d < 0 ? "scale(1,-1)" : "") + "translate(0," + (d - bands) * h + ")"; };
641
+
642
+ // Instantiate each copy of the path with different transforms.
643
+ var u = g.select("g").selectAll("use")
644
+ .data(d3.range(-1, -bands - 1, -1).concat(d3.range(1, bands + 1)), Number);
645
+
646
+ // TODO don't fudge the enter transition
647
+ u.enter().append("svg:use")
648
+ .attr("xlink:href", "#d3_chart_horizon_path" + id)
649
+ .attr("transform", function(d) { return transform(d + (d > 0 ? 1 : -1)); })
650
+ .style("fill", color)
651
+ .transition()
652
+ .duration(duration)
653
+ .attr("transform", transform);
654
+
655
+ u.transition()
656
+ .duration(duration)
657
+ .attr("transform", transform)
658
+ .style("fill", color);
659
+
660
+ u.exit().transition()
661
+ .duration(duration)
662
+ .attr("transform", transform)
663
+ .remove();
664
+
665
+ // Stash the new scales.
666
+ this.__chart__ = {x: x1, y: y1, id: id};
667
+ });
668
+ d3.timer.flush();
669
+ }
670
+
671
+ horizon.duration = function(x) {
672
+ if (!arguments.length) return duration;
673
+ duration = +x;
674
+ return horizon;
675
+ };
676
+
677
+ horizon.bands = function(x) {
678
+ if (!arguments.length) return bands;
679
+ bands = +x;
680
+ color.domain([-bands, 0, bands]);
681
+ return horizon;
682
+ };
683
+
684
+ horizon.mode = function(x) {
685
+ if (!arguments.length) return mode;
686
+ mode = x + "";
687
+ return horizon;
688
+ };
689
+
690
+ horizon.colors = function(x) {
691
+ if (!arguments.length) return color.range();
692
+ color.range(x);
693
+ return horizon;
694
+ };
695
+
696
+ horizon.interpolate = function(x) {
697
+ if (!arguments.length) return interpolate;
698
+ interpolate = x + "";
699
+ return horizon;
700
+ };
701
+
702
+ horizon.x = function(z) {
703
+ if (!arguments.length) return x;
704
+ x = z;
705
+ return horizon;
706
+ };
707
+
708
+ horizon.y = function(z) {
709
+ if (!arguments.length) return y;
710
+ y = z;
711
+ return horizon;
712
+ };
713
+
714
+ horizon.width = function(x) {
715
+ if (!arguments.length) return w;
716
+ w = +x;
717
+ return horizon;
718
+ };
719
+
720
+ horizon.height = function(x) {
721
+ if (!arguments.length) return h;
722
+ h = +x;
723
+ return horizon;
724
+ };
725
+
726
+ return horizon;
727
+ };
728
+
729
+ var d3_chart_horizonArea = d3.svg.area(),
730
+ d3_chart_horizonId = 0;
731
+
732
+ function d3_chart_horizonX(d) {
733
+ return d[0];
734
+ }
735
+
736
+ function d3_chart_horizonY(d) {
737
+ return d[1];
738
+ }
739
+ // Based on http://vis.stanford.edu/protovis/ex/qqplot.html
740
+ d3.chart.qq = function() {
741
+ var width = 1,
742
+ height = 1,
743
+ duration = 0,
744
+ domain = null,
745
+ tickFormat = null,
746
+ n = 100,
747
+ x = d3_chart_qqX,
748
+ y = d3_chart_qqY;
749
+
750
+ // For each small multiple…
751
+ function qq(g) {
752
+ g.each(function(d, i) {
753
+ var g = d3.select(this),
754
+ qx = d3_chart_qqQuantiles(n, x.call(this, d, i)),
755
+ qy = d3_chart_qqQuantiles(n, y.call(this, d, i)),
756
+ xd = domain && domain.call(this, d, i) || [d3.min(qx), d3.max(qx)], // new x-domain
757
+ yd = domain && domain.call(this, d, i) || [d3.min(qy), d3.max(qy)], // new y-domain
758
+ x0, // old x-scale
759
+ y0; // old y-scale
760
+
761
+ // Compute the new x-scale.
762
+ var x1 = d3.scale.linear()
763
+ .domain(xd)
764
+ .range([0, width]);
765
+
766
+ // Compute the new y-scale.
767
+ var y1 = d3.scale.linear()
768
+ .domain(yd)
769
+ .range([height, 0]);
770
+
771
+ // Retrieve the old scales, if this is an update.
772
+ if (this.__chart__) {
773
+ x0 = this.__chart__.x;
774
+ y0 = this.__chart__.y;
775
+ } else {
776
+ x0 = d3.scale.linear().domain([0, Infinity]).range(x1.range());
777
+ y0 = d3.scale.linear().domain([0, Infinity]).range(y1.range());
778
+ }
779
+
780
+ // Stash the new scales.
781
+ this.__chart__ = {x: x1, y: y1};
782
+
783
+ // Update diagonal line.
784
+ var diagonal = g.selectAll("line.diagonal")
785
+ .data([null]);
786
+
787
+ diagonal.enter().append("svg:line")
788
+ .attr("class", "diagonal")
789
+ .attr("x1", x1(yd[0]))
790
+ .attr("y1", y1(xd[0]))
791
+ .attr("x2", x1(yd[1]))
792
+ .attr("y2", y1(xd[1]));
793
+
794
+ diagonal.transition()
795
+ .duration(duration)
796
+ .attr("x1", x1(yd[0]))
797
+ .attr("y1", y1(xd[0]))
798
+ .attr("x2", x1(yd[1]))
799
+ .attr("y2", y1(xd[1]));
800
+
801
+ // Update quantile plots.
802
+ var circle = g.selectAll("circle")
803
+ .data(d3.range(n).map(function(i) {
804
+ return {x: qx[i], y: qy[i]};
805
+ }));
806
+
807
+ circle.enter().append("svg:circle")
808
+ .attr("class", "quantile")
809
+ .attr("r", 4.5)
810
+ .attr("cx", function(d) { return x0(d.x); })
811
+ .attr("cy", function(d) { return y0(d.y); })
812
+ .style("opacity", 1e-6)
813
+ .transition()
814
+ .duration(duration)
815
+ .attr("cx", function(d) { return x1(d.x); })
816
+ .attr("cy", function(d) { return y1(d.y); })
817
+ .style("opacity", 1);
818
+
819
+ circle.transition()
820
+ .duration(duration)
821
+ .attr("cx", function(d) { return x1(d.x); })
822
+ .attr("cy", function(d) { return y1(d.y); })
823
+ .style("opacity", 1);
824
+
825
+ circle.exit().transition()
826
+ .duration(duration)
827
+ .attr("cx", function(d) { return x1(d.x); })
828
+ .attr("cy", function(d) { return y1(d.y); })
829
+ .style("opacity", 1e-6)
830
+ .remove();
831
+
832
+ var xformat = tickFormat || x1.tickFormat(4),
833
+ yformat = tickFormat || y1.tickFormat(4),
834
+ tx = function(d) { return "translate(" + x1(d) + "," + height + ")"; },
835
+ ty = function(d) { return "translate(0," + y1(d) + ")"; };
836
+
837
+ // Update x-ticks.
838
+ var xtick = g.selectAll("g.x.tick")
839
+ .data(x1.ticks(4), function(d) {
840
+ return this.textContent || xformat(d);
841
+ });
842
+
843
+ var xtickEnter = xtick.enter().append("svg:g")
844
+ .attr("class", "x tick")
845
+ .attr("transform", function(d) { return "translate(" + x0(d) + "," + height + ")"; })
846
+ .style("opacity", 1e-6);
847
+
848
+ xtickEnter.append("svg:line")
849
+ .attr("y1", 0)
850
+ .attr("y2", -6);
851
+
852
+ xtickEnter.append("svg:text")
853
+ .attr("text-anchor", "middle")
854
+ .attr("dy", "1em")
855
+ .text(xformat);
856
+
857
+ // Transition the entering ticks to the new scale, x1.
858
+ xtickEnter.transition()
859
+ .duration(duration)
860
+ .attr("transform", tx)
861
+ .style("opacity", 1);
862
+
863
+ // Transition the updating ticks to the new scale, x1.
864
+ xtick.transition()
865
+ .duration(duration)
866
+ .attr("transform", tx)
867
+ .style("opacity", 1);
868
+
869
+ // Transition the exiting ticks to the new scale, x1.
870
+ xtick.exit().transition()
871
+ .duration(duration)
872
+ .attr("transform", tx)
873
+ .style("opacity", 1e-6)
874
+ .remove();
875
+
876
+ // Update ticks.
877
+ var ytick = g.selectAll("g.y.tick")
878
+ .data(y1.ticks(4), function(d) {
879
+ return this.textContent || yformat(d);
880
+ });
881
+
882
+ var ytickEnter = ytick.enter().append("svg:g")
883
+ .attr("class", "y tick")
884
+ .attr("transform", function(d) { return "translate(0," + y0(d) + ")"; })
885
+ .style("opacity", 1e-6);
886
+
887
+ ytickEnter.append("svg:line")
888
+ .attr("x1", 0)
889
+ .attr("x2", 6);
890
+
891
+ ytickEnter.append("svg:text")
892
+ .attr("text-anchor", "end")
893
+ .attr("dx", "-.5em")
894
+ .attr("dy", ".3em")
895
+ .text(yformat);
896
+
897
+ // Transition the entering ticks to the new scale, y1.
898
+ ytickEnter.transition()
899
+ .duration(duration)
900
+ .attr("transform", ty)
901
+ .style("opacity", 1);
902
+
903
+ // Transition the updating ticks to the new scale, y1.
904
+ ytick.transition()
905
+ .duration(duration)
906
+ .attr("transform", ty)
907
+ .style("opacity", 1);
908
+
909
+ // Transition the exiting ticks to the new scale, y1.
910
+ ytick.exit().transition()
911
+ .duration(duration)
912
+ .attr("transform", ty)
913
+ .style("opacity", 1e-6)
914
+ .remove();
915
+ });
916
+ }
917
+
918
+ qq.width = function(x) {
919
+ if (!arguments.length) return width;
920
+ width = x;
921
+ return qq;
922
+ };
923
+
924
+ qq.height = function(x) {
925
+ if (!arguments.length) return height;
926
+ height = x;
927
+ return qq;
928
+ };
929
+
930
+ qq.duration = function(x) {
931
+ if (!arguments.length) return duration;
932
+ duration = x;
933
+ return qq;
934
+ };
935
+
936
+ qq.domain = function(x) {
937
+ if (!arguments.length) return domain;
938
+ domain = x == null ? x : d3.functor(x);
939
+ return qq;
940
+ };
941
+
942
+ qq.count = function(z) {
943
+ if (!arguments.length) return n;
944
+ n = z;
945
+ return qq;
946
+ };
947
+
948
+ qq.x = function(z) {
949
+ if (!arguments.length) return x;
950
+ x = z;
951
+ return qq;
952
+ };
953
+
954
+ qq.y = function(z) {
955
+ if (!arguments.length) return y;
956
+ y = z;
957
+ return qq;
958
+ };
959
+
960
+ qq.tickFormat = function(x) {
961
+ if (!arguments.length) return tickFormat;
962
+ tickFormat = x;
963
+ return qq;
964
+ };
965
+
966
+ return qq;
967
+ };
968
+
969
+ function d3_chart_qqQuantiles(n, values) {
970
+ var m = values.length - 1;
971
+ values = values.slice().sort(d3.ascending);
972
+ return d3.range(n).map(function(i) {
973
+ return values[~~(i * m / n)];
974
+ });
975
+ }
976
+
977
+ function d3_chart_qqX(d) {
978
+ return d.x;
979
+ }
980
+
981
+ function d3_chart_qqY(d) {
982
+ return d.y;
983
+ }
984
+ })();