added blip name on blip hovering, fixed rings in the example
[tech-radar.git] / src / graphing / radar.js
1 tr.graphing.Radar = function (size, radar, toolTipDescription) {
2 var self, fib, svg, texts;
3
4 texts = [];
5 fib = new tr.util.Fib();
6
7 self = {};
8 self.svg = function () {
9 return svg;
10 }
11
12 function center () {
13 return Math.round(size/2);
14 }
15
16 function plotLines() {
17 svg.append('line')
18 .attr('x1', center())
19 .attr('y1', 0)
20 .attr('x2', center())
21 .attr('y2', size)
22 .attr('stroke-width', 14);
23
24 svg.append('line')
25 .attr('x1', 0)
26 .attr('y1', center())
27 .attr('x2', size)
28 .attr('y2', center())
29 .attr('stroke-width', 14);
30 };
31
32 function getRadius(cycles, i) {
33 var sequence = fib.sequence(cycles.length);
34 var total = fib.sum(cycles.length);
35 var sum = fib.sum(i);
36
37 return center() - (center() * sum / total);
38 }
39
40 function plotCircles(cycles) {
41 var increment;
42
43 cycles.forEach(function (cycle, i) {
44 svg.append('circle')
45 .attr('cx', center())
46 .attr('cy', center())
47 .attr('r', getRadius(cycles, i));
48 });
49 }
50
51 function plotTexts(cycles) {
52 var increment;
53
54 increment = Math.round(center() / cycles.length);
55
56 cycles.forEach(function (cycle, i) {
57 svg.append('text')
58 .attr('class', 'line-text')
59 .attr('y', center() + 4)
60 .attr('x', center() - getRadius(cycles, i) + 10)
61 .text(cycle.name());
62
63 svg.append('text')
64 .attr('class', 'line-text')
65 .attr('y', center() + 4)
66 .attr('x', center() + getRadius(cycles, i) - 10)
67 .attr('text-anchor', 'end')
68 .text(cycle.name());
69 });
70 };
71
72 function triangle(x, y, cssClass, group) {
73 var tsize, top, left, right, bottom, points;
74
75 tsize = 13
76 top = y - tsize;
77 left = (x - tsize + 1);
78 right = (x + tsize + 1);
79 bottom = (y + tsize - tsize / 2.5);
80
81 points = x + 1 + ',' + top + ' ' + left + ',' + bottom + ' ' + right + ',' + bottom;
82
83 return (group || svg).append('polygon')
84 .attr('points', points)
85 .attr('class', cssClass)
86 .attr('stroke-width', 1.5);
87 }
88
89 function circle(x, y, cssClass, group) {
90 return (group || svg).append('circle')
91 .attr('cx', x)
92 .attr('cy', y)
93 .attr('class', cssClass)
94 .attr('stroke-width', 1.5)
95 .attr('r', 10);
96 }
97
98 function plotBlips(cycles, quadrant, adjustX, adjustY, cssClass) {
99 var blips;
100 blips = quadrant.blips();
101 cycles.forEach(function (cycle, i) {
102 var maxRadius, minRadius, cycleBlips;
103
104 maxRadius = getRadius(cycles, i);
105 minRadius = (i == cycles.length - 1) ? 0: getRadius(cycles, i + 1);
106
107 var cycleBlips = blips.filter(function (blip) {
108 return blip.cycle() == cycle;
109 });
110
111 cycleBlips.forEach(function (blip) {
112 var angleInRad, radius;
113
114 var split = blip.name().split('');
115 var sum = split.reduce(function (p, c) { return p + c.charCodeAt(0); }, 0);
116 chance = new Chance(sum * cycle.name().length * blip.number());
117
118 angleInRad = Math.PI * chance.integer({ min: 13, max: 85 }) / 180;
119 radius = chance.floating({ min: minRadius + 25, max: maxRadius - 10 });
120
121 var x = center() + radius * Math.cos(angleInRad) * adjustX;
122 var y = center() + radius * Math.sin(angleInRad) * adjustY;
123
124 var group = svg.append('g').attr('class', 'blip-group');
125
126 if (blip.isNew()) {
127 triangle(x, y, cssClass, group);
128 } else {
129 circle(x, y, cssClass, group);
130 }
131
132 texts.push(function () {
133 var name;
134
135 name = svg.append('text')
136 .attr('x', x + 15)
137 .attr('y', y + 4)
138 .attr('class', 'blip-name')
139 .attr('text-anchor', 'left')
140 .text(blip.name())
141
142 group
143 .on('mouseover', function () { name.style('display', 'block'); })
144 .on('mouseout', function () { name.style('display', 'none'); });
145 });
146
147 group.append('text')
148 .attr('x', x)
149 .attr('y', y + 4)
150 .attr('class', 'blip-text')
151 .attr('text-anchor', 'middle')
152 .text(blip.number())
153 .append("svg:title")
154 .text(blip.name() + ((toolTipDescription && blip.description())
155 ? ': ' + blip.description().replace(/(<([^>]+)>)/ig, '')
156 : '' ))
157 });
158 });
159 };
160
161 function plotQuadrantNames(quadrants) {
162 function plotName(name, anchor, x, y, cssClass) {
163 svg.append('text')
164 .attr('x', x)
165 .attr('y', y)
166 .attr('class', cssClass)
167 .attr('text-anchor', anchor)
168 .text(name);
169 }
170
171 plotName(quadrants.I.name(), 'end', size - 10, 10, 'first')
172 plotName(quadrants.II.name(), 'start', 10, 10, 'second')
173 plotName(quadrants.III.name(), 'start', 10, size - 10, 'third')
174 plotName(quadrants.IV.name(), 'end', size -10, size - 10, 'fourth')
175 }
176
177 self.init = function (selector) {
178 svg = d3.select(selector || 'body').append("svg");
179 return self;
180 };
181
182 self.plot = function () {
183 var cycles, quadrants;
184
185 cycles = radar.cycles().reverse();
186 quadrants = radar.quadrants();
187
188 svg.attr('width', size).attr('height', size);
189
190 plotCircles(cycles);
191 plotLines();
192 plotTexts(cycles);
193
194 if (radar.hasQuadrants()) {
195 plotQuadrantNames(quadrants);
196 plotBlips(cycles, quadrants.I, 1, -1, 'first');
197 plotBlips(cycles, quadrants.II, -1, -1, 'second');
198 plotBlips(cycles, quadrants.III, -1, 1, 'third');
199 plotBlips(cycles, quadrants.IV, 1, 1, 'fourth');
200 }
201
202 texts.forEach(function (fn) {
203 fn();
204 });
205 };
206
207 return self;
208 };