added gh-pages publish task, added ref table to example
[tech-radar.git] / examples / tech-radar.js
1 /**
2 * tech-radar
3 * @version v0.1.6
4 */
5 var tr = tr || {};
6 tr.models = {};
7 tr.graphing = {};
8 tr.util = {};
9
10 tr.graphing.Radar = function (size, radar) {
11 var self, fib, svg;
12
13 fib = new tr.util.Fib();
14
15 self = {};
16 self.svg = function () {
17 return svg;
18 }
19
20 function center () {
21 return Math.round(size/2);
22 }
23
24 function plotLines() {
25 svg.append('line')
26 .attr('x1', center())
27 .attr('y1', 0)
28 .attr('x2', center())
29 .attr('y2', size)
30 .attr('stroke-width', 14);
31
32 svg.append('line')
33 .attr('x1', 0)
34 .attr('y1', center())
35 .attr('x2', size)
36 .attr('y2', center())
37 .attr('stroke-width', 14);
38 };
39
40 function getRadius(cycles, i) {
41 var sequence = fib.sequence(cycles.length);
42 var total = fib.sum(cycles.length);
43 var sum = fib.sum(i);
44
45 return center() - (center() * sum / total);
46 }
47
48 function plotCircles(cycles) {
49 var increment;
50
51 cycles.forEach(function (cycle, i) {
52 svg.append('circle')
53 .attr('cx', center())
54 .attr('cy', center())
55 .attr('r', getRadius(cycles, i));
56 });
57 }
58
59 function plotTexts(cycles) {
60 var increment;
61
62 increment = Math.round(center() / cycles.length);
63
64 cycles.forEach(function (cycle, i) {
65 svg.append('text')
66 .attr('class', 'line-text')
67 .attr('y', center() + 4)
68 .attr('x', center() - getRadius(cycles, i) + 10)
69 .text(cycle.name());
70
71 svg.append('text')
72 .attr('class', 'line-text')
73 .attr('y', center() + 4)
74 .attr('x', center() + getRadius(cycles, i) - 10)
75 .attr('text-anchor', 'end')
76 .text(cycle.name());
77 });
78 };
79
80 function triangle(x, y, cssClass) {
81 var tsize, top, left, right, bottom, points;
82
83 tsize = 13
84 top = y - tsize;
85 left = (x - tsize + 1);
86 right = (x + tsize + 1);
87 bottom = (y + tsize - tsize / 2.5);
88
89 points = x + 1 + ',' + top + ' ' + left + ',' + bottom + ' ' + right + ',' + bottom;
90
91 return svg.append('polygon')
92 .attr('points', points)
93 .attr('class', cssClass)
94 .attr('stroke-width', 1.5);
95 }
96
97 function circle(x, y, cssClass) {
98 svg.append('circle')
99 .attr('cx', x)
100 .attr('cy', y)
101 .attr('class', cssClass)
102 .attr('stroke-width', 1.5)
103 .attr('r', 10);
104 }
105
106 function plotBlips(cycles, quadrant, adjustX, adjustY, cssClass) {
107 var blips;
108 blips = quadrant.blips();
109 cycles.forEach(function (cycle, i) {
110 var maxRadius, minRadius, cycleBlips;
111
112 maxRadius = getRadius(cycles, i);
113 minRadius = (i == cycles.length - 1) ? 0: getRadius(cycles, i + 1);
114
115 var cycleBlips = blips.filter(function (blip) {
116 return blip.cycle() == cycle;
117 });
118
119 cycleBlips.forEach(function (blip) {
120 var angleInRad, radius;
121
122 var split = blip.name().split('');
123 var sum = split.reduce(function (p, c) { return p + c.charCodeAt(0); }, 0);
124 chance = new Chance(sum * cycle.name().length * blip.number());
125
126 angleInRad = Math.PI * chance.integer({ min: 13, max: 85 }) / 180;
127 radius = chance.floating({ min: minRadius + 25, max: maxRadius - 10 });
128
129 var x = center() + radius * Math.cos(angleInRad) * adjustX;
130 var y = center() + radius * Math.sin(angleInRad) * adjustY;
131
132 if (blip.isNew()) {
133 triangle(x, y, cssClass);
134 } else {
135 circle(x, y, cssClass);
136 }
137
138 svg.append('text')
139 .attr('x', x)
140 .attr('y', y + 4)
141 .attr('class', 'blip-text')
142 .attr('text-anchor', 'middle')
143 .text(blip.number())
144 });
145 });
146 };
147
148 function plotQuadrantNames(quadrants) {
149 function plotName(name, anchor, x, y, cssClass) {
150 svg.append('text')
151 .attr('x', x)
152 .attr('y', y)
153 .attr('class', cssClass)
154 .attr('text-anchor', anchor)
155 .text(name);
156 }
157
158 plotName(quadrants.I.name(), 'end', size - 10, 10, 'first')
159 plotName(quadrants.II.name(), 'start', 10, 10, 'second')
160 plotName(quadrants.III.name(), 'start', 10, size - 10, 'third')
161 plotName(quadrants.IV.name(), 'end', size -10, size - 10, 'fourth')
162 }
163
164 self.init = function (selector) {
165 svg = d3.select(selector || 'body').append("svg");
166 return self;
167 };
168
169 self.plot = function () {
170 var cycles, quadrants;
171
172 cycles = radar.cycles().reverse();
173 quadrants = radar.quadrants();
174
175 svg.attr('width', size).attr('height', size);
176
177 plotCircles(cycles);
178 plotLines();
179 plotTexts(cycles);
180
181 if (radar.hasQuadrants()) {
182 plotQuadrantNames(quadrants);
183 plotBlips(cycles, quadrants.I, 1, -1, 'first');
184 plotBlips(cycles, quadrants.II, -1, -1, 'second');
185 plotBlips(cycles, quadrants.III, -1, 1, 'third');
186 plotBlips(cycles, quadrants.IV, 1, 1, 'fourth');
187 }
188 };
189
190 return self;
191 };
192
193 tr.graphing.RefTable = function (radar) {
194 var self = {};
195 var injectionElement;
196
197 function blipsByCycle () {
198 // set up empty blip arrays for each cycle
199 var cycles = {};
200 radar.cycles()
201 .map(function (cycle) {
202 return {
203 order: cycle.order(),
204 name: cycle.name()
205 };
206 })
207 .sort(function (a, b) {
208 if (a.order === b.order) {
209 return 0;
210 } else if (a.order < b.order) {
211 return -1;
212 } else {
213 return 1;
214 }
215 })
216 .forEach(function (cycle) {
217 cycles[cycle.name] = [];
218 });
219
220 // group blips by cycle
221 var blips = [];
222 var quadrants = radar.quadrants();
223 Object.keys(quadrants).forEach(function (quadrant) {
224 blips = blips.concat(quadrants[quadrant].blips());
225 });
226
227 blips.forEach(function (blip) {
228 cycles[blip.cycle().name()].push(blip);
229 });
230
231 return cycles;
232 }
233
234 self.init = function (selector) {
235 injectionElement = document.querySelector(selector || 'body');
236 return self;
237 };
238
239 self.render = function () {
240 var blips = blipsByCycle();
241
242 var html = '<table class="radar-ref-table">';
243
244 Object.keys(blips).forEach(function (cycle) {
245 html += '<tr class="radar-ref-status-group"><td colspan="3">' + cycle + '</td></tr>';
246
247 blips[cycle].forEach(function (blip) {
248 html += '<tr>' +
249 '<td>' + blip.number() + '</td>' +
250 '<td>' + blip.name() + '</td>' +
251 '<td>' + blip.description() + '</td>' +
252 '</tr>';
253 });
254 });
255
256 html += '</table>';
257
258 injectionElement.innerHTML = html;
259 };
260
261 return self;
262 };
263
264 tr.models.Blip = function (name, cycle, isNew, description) {
265 var self, number;
266
267 self = {};
268 number = -1;
269
270 self.name = function () {
271 return name;
272 };
273
274 self.description = function () {
275 return description || '';
276 };
277
278 self.isNew = function () {
279 return isNew;
280 };
281
282 self.cycle = function () {
283 return cycle;
284 };
285
286 self.number = function () {
287 return number;
288 };
289
290 self.setNumber = function (newNumber) {
291 number = newNumber;
292 };
293
294 return self;
295 };
296
297 tr.models.Cycle = function (name, order) {
298 var self = {};
299
300 self.name = function () {
301 return name;
302 };
303
304 self.order = function () {
305 return order;
306 };
307
308 return self;
309 };
310
311 tr.models.Quadrant = function (name) {
312 var self, blips;
313
314 self = {};
315 blips = [];
316
317 self.name = function () {
318 return name;
319 };
320
321 self.add = function (newBlips) {
322 if (Array.isArray(newBlips)) {
323 blips = blips.concat(newBlips);
324 } else {
325 blips.push(newBlips);
326 }
327 };
328
329 self.blips = function () {
330 return blips.slice(0);
331 };
332
333 return self;
334 };
335
336 tr.models.Radar = function() {
337 var self, quadrants, blipNumber;
338
339 blipNumber = 0;
340 quadrants = { I: null, II: null, III: null, IV: null };
341 self = {};
342
343 function setNumbers(blips) {
344 blips.forEach(function (blip) {
345 blip.setNumber(++blipNumber);
346 });
347 }
348
349 self.setFirstQuadrant = function (quadrant) {
350 quadrants.I = quadrant;
351 setNumbers(quadrants.I.blips());
352 };
353
354 self.setSecondQuadrant = function (quadrant) {
355 quadrants.II = quadrant;
356 setNumbers(quadrants.II.blips());
357 };
358
359 self.setThirdQuadrant = function (quadrant) {
360 quadrants.III = quadrant;
361 setNumbers(quadrants.III.blips());
362 };
363
364 self.setFourthQuadrant = function (quadrant) {
365 quadrants.IV = quadrant;
366 setNumbers(quadrants.IV.blips());
367 };
368
369 function allQuadrants() {
370 var all = [];
371
372 for (var p in quadrants) {
373 if (quadrants.hasOwnProperty(p) && quadrants[p] != null) {
374 all.push(quadrants[p]);
375 }
376 }
377
378 return all;
379 }
380
381 function allBlips() {
382 return allQuadrants().reduce(function (blips, quadrant) {
383 return blips.concat(quadrant.blips());
384 }, []);
385 }
386
387 self.hasQuadrants = function () {
388 return !!quadrants.I || !!quadrants.II || !!quadrants.III || !!quadrants.IV;
389 }
390
391 self.cycles = function () {
392 var cycleHash, cycleArray;
393
394 cycleArray = [];
395 cycleHash = {};
396
397 allBlips().forEach(function (blip) {
398 cycleHash[blip.cycle().name()] = blip.cycle();
399 });
400
401 for (var p in cycleHash) {
402 if (cycleHash.hasOwnProperty(p)) {
403 cycleArray.push(cycleHash[p]);
404 }
405 }
406
407 return cycleArray.slice(0).sort(function (a, b) { return a.order() - b.order(); });
408 };
409
410 self.quadrants = function () {
411 return quadrants;
412 };
413
414 return self;
415 };
416
417 tr.util.Fib = function () {
418 var self = {};
419
420 self.sequence = function (length) {
421 var result = [0, 1];
422
423 for (var i = 2; i < length; i++) {
424 result[i] = result[i-2] + result[i-1];
425 }
426
427 return result;
428 };
429
430 self.sum = function (length) {
431 if (length === 0) { return 0; }
432 if (length === 1) { return 1; }
433
434 return self.sequence(length + 1).reduce(function (previous, current) {
435 return previous + current;
436 }, 0);
437 };
438
439 return self;
440 };