|
1 function Streamgraph($selector) { |
|
2 |
|
3 /* Constants */ |
|
4 |
|
5 var VMARGIN = 3, |
|
6 YEARSHEIGHT = 20, |
|
7 STARTTIME = new Date(2007,6,1), |
|
8 ENDTIME = new Date(), |
|
9 CURVE = .25, |
|
10 DATEPADDING = 10, |
|
11 COLORS = [ "#943a23", "#fbee97", "#cfbb95", "#da9761", "#ba5036" ], |
|
12 SELECTEDCOLOR = "#c51810"; |
|
13 |
|
14 /* Generating random data */ |
|
15 |
|
16 var data = [], |
|
17 clustercount = 12, |
|
18 slicecount = 20, |
|
19 maxdata = 10, |
|
20 randpart = 4, |
|
21 dampfactor = .333; |
|
22 for (var i = 0; i < clustercount; i++) { |
|
23 var line = [], |
|
24 peaktime = Math.floor(Math.random() * slicecount); |
|
25 for (var j = 0; j < slicecount; j++) { |
|
26 var point = Math.min(maxdata, Math.max(0, (Math.random() - .5) * randpart + Math.max(0, maxdata * (1 - dampfactor * Math.abs(j - peaktime))))); |
|
27 line.push(point); |
|
28 } |
|
29 data.push(line); |
|
30 } |
|
31 |
|
32 /* Calculating scales and positions */ |
|
33 |
|
34 var width = $selector.width(), |
|
35 height = $selector.height(), |
|
36 transp = _.zip.apply( _, data ), |
|
37 cumulative = _(transp).map(function(column) { |
|
38 var total = 0; |
|
39 return _(column).map(function(point) { |
|
40 return total += point; |
|
41 }); |
|
42 }), |
|
43 sums = _(cumulative).map(function(column) { |
|
44 return _(column).last(); |
|
45 }) |
|
46 maxcol = _(sums).max(), |
|
47 streamheight = height - YEARSHEIGHT, |
|
48 yscale = (streamheight - 2 * VMARGIN) / maxcol, |
|
49 centery = streamheight / 2, |
|
50 xscale = width / (transp.length - 1), |
|
51 txscale = width / (ENDTIME - STARTTIME), |
|
52 coords = _(data).map(function(line, lineindex) { |
|
53 return { |
|
54 points : _(line).map(function(point, colindex) { |
|
55 var lowercumul = lineindex ? cumulative[colindex][lineindex - 1] : 0, |
|
56 uppercumul = cumulative[colindex][lineindex]; |
|
57 return { |
|
58 data: point, |
|
59 x: xscale * colindex, |
|
60 lowery: centery + yscale * ( ( sums[colindex] / 2 ) - lowercumul ), |
|
61 uppery: centery + yscale * ( ( sums[colindex] / 2 ) - uppercumul ), |
|
62 } |
|
63 }) |
|
64 } |
|
65 }), |
|
66 _(coords).each(function(line) { |
|
67 var lowerline = _(line.points).reduce(function(path, point, colindex) { |
|
68 var res = path; |
|
69 if (colindex) { |
|
70 res += "," + (point.x - CURVE * xscale) + "," + point.lowery + "," + point.x + "," + point.lowery; |
|
71 } else { |
|
72 res += "M" + point.x + "," + point.lowery; |
|
73 } |
|
74 if (colindex < line.points.length - 1) { |
|
75 res += "C" + (point.x + CURVE * xscale) + "," + point.lowery; |
|
76 } |
|
77 return res; |
|
78 }, ""); |
|
79 var upperline = _(line.points).reduceRight(function(path, point, colindex) { |
|
80 var res = path; |
|
81 if (colindex < line.points.length - 1) { |
|
82 res += "," + (point.x + CURVE * xscale) + "," + point.uppery + "," + point.x + "," + point.uppery; |
|
83 } else { |
|
84 res += "L" + point.x + "," + point.uppery; |
|
85 } |
|
86 if (colindex) { |
|
87 res += "C" + (point.x - CURVE * xscale) + "," + point.uppery; |
|
88 } |
|
89 return res; |
|
90 }, ""); |
|
91 line.path = lowerline + upperline; |
|
92 }); |
|
93 |
|
94 /* Drawing streamgraph*/ |
|
95 |
|
96 var paper = new Raphael($selector[0]); |
|
97 |
|
98 _(coords).each(function(line, index) { |
|
99 line.color = COLORS[index % COLORS.length]; |
|
100 line.surface = paper.path(line.path); |
|
101 line.surface.attr({ |
|
102 stroke: "none", |
|
103 fill: line.color |
|
104 }); |
|
105 }); |
|
106 |
|
107 /* Drawing years */ |
|
108 |
|
109 paper.path("M0," + (height - YEARSHEIGHT) + "," + width + "," + (height - YEARSHEIGHT)) |
|
110 var lastyear = ENDTIME.getFullYear(); |
|
111 for (var i = STARTTIME.getFullYear(); i <= lastyear; i++) { |
|
112 var x = txscale * (new Date(i,0,1) - STARTTIME); |
|
113 paper.path("M" + x + ",0," + x + "," + height); |
|
114 var x = txscale * (new Date(i,6,1) - STARTTIME); |
|
115 paper.text(x, height - .5 * YEARSHEIGHT, i) |
|
116 .attr({ |
|
117 "text-anchor": "middle", |
|
118 "font-family": "Times New Roman, serif", |
|
119 "font-size": "14px" |
|
120 }); |
|
121 } |
|
122 |
|
123 /* Drawing range window */ |
|
124 |
|
125 var carregauche = paper.rect(width,-1,width,(2+height)), |
|
126 carredroite = paper.rect(-width,-1,width,(2+height)), |
|
127 attrcarres = { |
|
128 fill: "#333333", |
|
129 "fill-opacity": .5, |
|
130 stroke: SELECTEDCOLOR |
|
131 }; |
|
132 carregauche.attr(attrcarres); |
|
133 carredroite.attr(attrcarres); |
|
134 |
|
135 var rangerect = paper.rect(0, (height - YEARSHEIGHT), width, YEARSHEIGHT); |
|
136 rangerect.attr({ |
|
137 fill: SELECTEDCOLOR, |
|
138 stroke: "none" |
|
139 }); |
|
140 |
|
141 function datetext(date) { |
|
142 var d = new Date(date), |
|
143 m = 1+d.getMonth(), |
|
144 y = d.getFullYear(); |
|
145 return ((m < 10 ? "0" : "") + m + "/" + y); |
|
146 } |
|
147 |
|
148 var startdate = paper.text(DATEPADDING, height - .5 * YEARSHEIGHT, datetext(STARTTIME)); |
|
149 startdate.attr({ |
|
150 fill: "#ffffff", |
|
151 "text-anchor": "start" |
|
152 }); |
|
153 var enddate = paper.text(width - DATEPADDING, height - .5 * YEARSHEIGHT, datetext(ENDTIME)); |
|
154 enddate.attr({ |
|
155 fill: "#ffffff", |
|
156 "text-anchor": "end" |
|
157 }); |
|
158 |
|
159 /* Redrawing time slices for rollover effect */ |
|
160 |
|
161 _(coords).each(function(line, index) { |
|
162 line.mousesurface = paper.path(line.path); |
|
163 console.log(line.mousesurface); |
|
164 line.mousesurface.attr({ |
|
165 stroke: "none", |
|
166 fill: line.color, |
|
167 opacity: .01 |
|
168 }).mouseover(function() { |
|
169 line.surface.attr({ |
|
170 fill: SELECTEDCOLOR |
|
171 }) |
|
172 }).mouseout(function() { |
|
173 line.surface.attr({ |
|
174 fill: line.color |
|
175 }) |
|
176 }); |
|
177 }); |
|
178 |
|
179 /* Returning a handler for slide value change */ |
|
180 |
|
181 this.slidevalues = function(left, right) { |
|
182 left = left || 0; |
|
183 right = right || width; |
|
184 carregauche.attr({x: left - width}); |
|
185 carredroite.attr({x: right}); |
|
186 startdate.attr({ |
|
187 x: DATEPADDING + left, |
|
188 text: datetext(STARTTIME.valueOf() + left / txscale) |
|
189 }); |
|
190 enddate.attr({ |
|
191 x: right - DATEPADDING, |
|
192 text: datetext(STARTTIME.valueOf() + right / txscale) |
|
193 }); |
|
194 rangerect.attr({ |
|
195 x: left, |
|
196 width: right - left |
|
197 }); |
|
198 } |
|
199 |
|
200 } |
|
201 |
|
202 $(function() { |
|
203 window.streamgraph = new Streamgraph($(".streamgraph")); |
|
204 streamgraph.slidevalues.apply(streamgraph,$("#slider-range").dragslider("values")); |
|
205 }) |