Mercurial > hg > soundsoftware-site
comparison lib/SVG/Graph/Line.rb @ 0:513646585e45
* Import Redmine trunk SVN rev 3859
| author | Chris Cannam |
|---|---|
| date | Fri, 23 Jul 2010 15:52:44 +0100 |
| parents | |
| children |
comparison
equal
deleted
inserted
replaced
| -1:000000000000 | 0:513646585e45 |
|---|---|
| 1 require 'SVG/Graph/Graph' | |
| 2 | |
| 3 module SVG | |
| 4 module Graph | |
| 5 # === Create presentation quality SVG line graphs easily | |
| 6 # | |
| 7 # = Synopsis | |
| 8 # | |
| 9 # require 'SVG/Graph/Line' | |
| 10 # | |
| 11 # fields = %w(Jan Feb Mar); | |
| 12 # data_sales_02 = [12, 45, 21] | |
| 13 # data_sales_03 = [15, 30, 40] | |
| 14 # | |
| 15 # graph = SVG::Graph::Line.new({ | |
| 16 # :height => 500, | |
| 17 # :width => 300, | |
| 18 # :fields => fields, | |
| 19 # }) | |
| 20 # | |
| 21 # graph.add_data({ | |
| 22 # :data => data_sales_02, | |
| 23 # :title => 'Sales 2002', | |
| 24 # }) | |
| 25 # | |
| 26 # graph.add_data({ | |
| 27 # :data => data_sales_03, | |
| 28 # :title => 'Sales 2003', | |
| 29 # }) | |
| 30 # | |
| 31 # print "Content-type: image/svg+xml\r\n\r\n"; | |
| 32 # print graph.burn(); | |
| 33 # | |
| 34 # = Description | |
| 35 # | |
| 36 # This object aims to allow you to easily create high quality | |
| 37 # SVG line graphs. You can either use the default style sheet | |
| 38 # or supply your own. Either way there are many options which can | |
| 39 # be configured to give you control over how the graph is | |
| 40 # generated - with or without a key, data elements at each point, | |
| 41 # title, subtitle etc. | |
| 42 # | |
| 43 # = Examples | |
| 44 # | |
| 45 # http://www.germane-software/repositories/public/SVG/test/single.rb | |
| 46 # | |
| 47 # = Notes | |
| 48 # | |
| 49 # The default stylesheet handles upto 10 data sets, if you | |
| 50 # use more you must create your own stylesheet and add the | |
| 51 # additional settings for the extra data sets. You will know | |
| 52 # if you go over 10 data sets as they will have no style and | |
| 53 # be in black. | |
| 54 # | |
| 55 # = See also | |
| 56 # | |
| 57 # * SVG::Graph::Graph | |
| 58 # * SVG::Graph::BarHorizontal | |
| 59 # * SVG::Graph::Bar | |
| 60 # * SVG::Graph::Pie | |
| 61 # * SVG::Graph::Plot | |
| 62 # * SVG::Graph::TimeSeries | |
| 63 # | |
| 64 # == Author | |
| 65 # | |
| 66 # Sean E. Russell <serATgermaneHYPHENsoftwareDOTcom> | |
| 67 # | |
| 68 # Copyright 2004 Sean E. Russell | |
| 69 # This software is available under the Ruby license[LICENSE.txt] | |
| 70 # | |
| 71 class Line < SVG::Graph::Graph | |
| 72 # Show a small circle on the graph where the line | |
| 73 # goes from one point to the next. | |
| 74 attr_accessor :show_data_points | |
| 75 # Accumulates each data set. (i.e. Each point increased by sum of | |
| 76 # all previous series at same point). Default is 0, set to '1' to show. | |
| 77 attr_accessor :stacked | |
| 78 # Fill in the area under the plot if true | |
| 79 attr_accessor :area_fill | |
| 80 | |
| 81 # The constructor takes a hash reference, fields (the names for each | |
| 82 # field on the X axis) MUST be set, all other values are defaulted to | |
| 83 # those shown above - with the exception of style_sheet which defaults | |
| 84 # to using the internal style sheet. | |
| 85 def initialize config | |
| 86 raise "fields was not supplied or is empty" unless config[:fields] && | |
| 87 config[:fields].kind_of?(Array) && | |
| 88 config[:fields].length > 0 | |
| 89 super | |
| 90 end | |
| 91 | |
| 92 # In addition to the defaults set in Graph::initialize, sets | |
| 93 # [show_data_points] true | |
| 94 # [show_data_values] true | |
| 95 # [stacked] false | |
| 96 # [area_fill] false | |
| 97 def set_defaults | |
| 98 init_with( | |
| 99 :show_data_points => true, | |
| 100 :show_data_values => true, | |
| 101 :stacked => false, | |
| 102 :area_fill => false | |
| 103 ) | |
| 104 | |
| 105 self.top_align = self.top_font = self.right_align = self.right_font = 1 | |
| 106 end | |
| 107 | |
| 108 protected | |
| 109 | |
| 110 def max_value | |
| 111 max = 0 | |
| 112 | |
| 113 if (stacked == true) then | |
| 114 sums = Array.new(@config[:fields].length).fill(0) | |
| 115 | |
| 116 @data.each do |data| | |
| 117 sums.each_index do |i| | |
| 118 sums[i] += data[:data][i].to_f | |
| 119 end | |
| 120 end | |
| 121 | |
| 122 max = sums.max | |
| 123 else | |
| 124 max = @data.collect{|x| x[:data].max}.max | |
| 125 end | |
| 126 | |
| 127 return max | |
| 128 end | |
| 129 | |
| 130 def min_value | |
| 131 min = 0 | |
| 132 | |
| 133 if (min_scale_value.nil? == false) then | |
| 134 min = min_scale_value | |
| 135 elsif (stacked == true) then | |
| 136 min = @data[-1][:data].min | |
| 137 else | |
| 138 min = @data.collect{|x| x[:data].min}.min | |
| 139 end | |
| 140 | |
| 141 return min | |
| 142 end | |
| 143 | |
| 144 def get_x_labels | |
| 145 @config[:fields] | |
| 146 end | |
| 147 | |
| 148 def calculate_left_margin | |
| 149 super | |
| 150 label_left = @config[:fields][0].length / 2 * font_size * 0.6 | |
| 151 @border_left = label_left if label_left > @border_left | |
| 152 end | |
| 153 | |
| 154 def get_y_labels | |
| 155 maxvalue = max_value | |
| 156 minvalue = min_value | |
| 157 range = maxvalue - minvalue | |
| 158 top_pad = range == 0 ? 10 : range / 20.0 | |
| 159 scale_range = (maxvalue + top_pad) - minvalue | |
| 160 | |
| 161 scale_division = scale_divisions || (scale_range / 10.0) | |
| 162 | |
| 163 if scale_integers | |
| 164 scale_division = scale_division < 1 ? 1 : scale_division.round | |
| 165 end | |
| 166 | |
| 167 rv = [] | |
| 168 maxvalue = maxvalue%scale_division == 0 ? | |
| 169 maxvalue : maxvalue + scale_division | |
| 170 minvalue.step( maxvalue, scale_division ) {|v| rv << v} | |
| 171 return rv | |
| 172 end | |
| 173 | |
| 174 def calc_coords(field, value, width = field_width, height = field_height) | |
| 175 coords = {:x => 0, :y => 0} | |
| 176 coords[:x] = width * field | |
| 177 coords[:y] = @graph_height - value * height | |
| 178 | |
| 179 return coords | |
| 180 end | |
| 181 | |
| 182 def draw_data | |
| 183 minvalue = min_value | |
| 184 fieldheight = (@graph_height.to_f - font_size*2*top_font) / | |
| 185 (get_y_labels.max - get_y_labels.min) | |
| 186 fieldwidth = field_width | |
| 187 line = @data.length | |
| 188 | |
| 189 prev_sum = Array.new(@config[:fields].length).fill(0) | |
| 190 cum_sum = Array.new(@config[:fields].length).fill(-minvalue) | |
| 191 | |
| 192 for data in @data.reverse | |
| 193 lpath = "" | |
| 194 apath = "" | |
| 195 | |
| 196 if not stacked then cum_sum.fill(-minvalue) end | |
| 197 | |
| 198 data[:data].each_index do |i| | |
| 199 cum_sum[i] += data[:data][i] | |
| 200 | |
| 201 c = calc_coords(i, cum_sum[i], fieldwidth, fieldheight) | |
| 202 | |
| 203 lpath << "#{c[:x]} #{c[:y]} " | |
| 204 end | |
| 205 | |
| 206 if area_fill | |
| 207 if stacked then | |
| 208 (prev_sum.length - 1).downto 0 do |i| | |
| 209 c = calc_coords(i, prev_sum[i], fieldwidth, fieldheight) | |
| 210 | |
| 211 apath << "#{c[:x]} #{c[:y]} " | |
| 212 end | |
| 213 | |
| 214 c = calc_coords(0, prev_sum[0], fieldwidth, fieldheight) | |
| 215 else | |
| 216 apath = "V#@graph_height" | |
| 217 c = calc_coords(0, 0, fieldwidth, fieldheight) | |
| 218 end | |
| 219 | |
| 220 @graph.add_element("path", { | |
| 221 "d" => "M#{c[:x]} #{c[:y]} L" + lpath + apath + "Z", | |
| 222 "class" => "fill#{line}" | |
| 223 }) | |
| 224 end | |
| 225 | |
| 226 @graph.add_element("path", { | |
| 227 "d" => "M0 #@graph_height L" + lpath, | |
| 228 "class" => "line#{line}" | |
| 229 }) | |
| 230 | |
| 231 if show_data_points || show_data_values | |
| 232 cum_sum.each_index do |i| | |
| 233 if show_data_points | |
| 234 @graph.add_element( "circle", { | |
| 235 "cx" => (fieldwidth * i).to_s, | |
| 236 "cy" => (@graph_height - cum_sum[i] * fieldheight).to_s, | |
| 237 "r" => "2.5", | |
| 238 "class" => "dataPoint#{line}" | |
| 239 }) | |
| 240 end | |
| 241 make_datapoint_text( | |
| 242 fieldwidth * i, | |
| 243 @graph_height - cum_sum[i] * fieldheight - 6, | |
| 244 cum_sum[i] + minvalue | |
| 245 ) | |
| 246 end | |
| 247 end | |
| 248 | |
| 249 prev_sum = cum_sum.dup | |
| 250 line -= 1 | |
| 251 end | |
| 252 end | |
| 253 | |
| 254 | |
| 255 def get_css | |
| 256 return <<EOL | |
| 257 /* default line styles */ | |
| 258 .line1{ | |
| 259 fill: none; | |
| 260 stroke: #ff0000; | |
| 261 stroke-width: 1px; | |
| 262 } | |
| 263 .line2{ | |
| 264 fill: none; | |
| 265 stroke: #0000ff; | |
| 266 stroke-width: 1px; | |
| 267 } | |
| 268 .line3{ | |
| 269 fill: none; | |
| 270 stroke: #00ff00; | |
| 271 stroke-width: 1px; | |
| 272 } | |
| 273 .line4{ | |
| 274 fill: none; | |
| 275 stroke: #ffcc00; | |
| 276 stroke-width: 1px; | |
| 277 } | |
| 278 .line5{ | |
| 279 fill: none; | |
| 280 stroke: #00ccff; | |
| 281 stroke-width: 1px; | |
| 282 } | |
| 283 .line6{ | |
| 284 fill: none; | |
| 285 stroke: #ff00ff; | |
| 286 stroke-width: 1px; | |
| 287 } | |
| 288 .line7{ | |
| 289 fill: none; | |
| 290 stroke: #00ffff; | |
| 291 stroke-width: 1px; | |
| 292 } | |
| 293 .line8{ | |
| 294 fill: none; | |
| 295 stroke: #ffff00; | |
| 296 stroke-width: 1px; | |
| 297 } | |
| 298 .line9{ | |
| 299 fill: none; | |
| 300 stroke: #ccc6666; | |
| 301 stroke-width: 1px; | |
| 302 } | |
| 303 .line10{ | |
| 304 fill: none; | |
| 305 stroke: #663399; | |
| 306 stroke-width: 1px; | |
| 307 } | |
| 308 .line11{ | |
| 309 fill: none; | |
| 310 stroke: #339900; | |
| 311 stroke-width: 1px; | |
| 312 } | |
| 313 .line12{ | |
| 314 fill: none; | |
| 315 stroke: #9966FF; | |
| 316 stroke-width: 1px; | |
| 317 } | |
| 318 /* default fill styles */ | |
| 319 .fill1{ | |
| 320 fill: #cc0000; | |
| 321 fill-opacity: 0.2; | |
| 322 stroke: none; | |
| 323 } | |
| 324 .fill2{ | |
| 325 fill: #0000cc; | |
| 326 fill-opacity: 0.2; | |
| 327 stroke: none; | |
| 328 } | |
| 329 .fill3{ | |
| 330 fill: #00cc00; | |
| 331 fill-opacity: 0.2; | |
| 332 stroke: none; | |
| 333 } | |
| 334 .fill4{ | |
| 335 fill: #ffcc00; | |
| 336 fill-opacity: 0.2; | |
| 337 stroke: none; | |
| 338 } | |
| 339 .fill5{ | |
| 340 fill: #00ccff; | |
| 341 fill-opacity: 0.2; | |
| 342 stroke: none; | |
| 343 } | |
| 344 .fill6{ | |
| 345 fill: #ff00ff; | |
| 346 fill-opacity: 0.2; | |
| 347 stroke: none; | |
| 348 } | |
| 349 .fill7{ | |
| 350 fill: #00ffff; | |
| 351 fill-opacity: 0.2; | |
| 352 stroke: none; | |
| 353 } | |
| 354 .fill8{ | |
| 355 fill: #ffff00; | |
| 356 fill-opacity: 0.2; | |
| 357 stroke: none; | |
| 358 } | |
| 359 .fill9{ | |
| 360 fill: #cc6666; | |
| 361 fill-opacity: 0.2; | |
| 362 stroke: none; | |
| 363 } | |
| 364 .fill10{ | |
| 365 fill: #663399; | |
| 366 fill-opacity: 0.2; | |
| 367 stroke: none; | |
| 368 } | |
| 369 .fill11{ | |
| 370 fill: #339900; | |
| 371 fill-opacity: 0.2; | |
| 372 stroke: none; | |
| 373 } | |
| 374 .fill12{ | |
| 375 fill: #9966FF; | |
| 376 fill-opacity: 0.2; | |
| 377 stroke: none; | |
| 378 } | |
| 379 /* default line styles */ | |
| 380 .key1,.dataPoint1{ | |
| 381 fill: #ff0000; | |
| 382 stroke: none; | |
| 383 stroke-width: 1px; | |
| 384 } | |
| 385 .key2,.dataPoint2{ | |
| 386 fill: #0000ff; | |
| 387 stroke: none; | |
| 388 stroke-width: 1px; | |
| 389 } | |
| 390 .key3,.dataPoint3{ | |
| 391 fill: #00ff00; | |
| 392 stroke: none; | |
| 393 stroke-width: 1px; | |
| 394 } | |
| 395 .key4,.dataPoint4{ | |
| 396 fill: #ffcc00; | |
| 397 stroke: none; | |
| 398 stroke-width: 1px; | |
| 399 } | |
| 400 .key5,.dataPoint5{ | |
| 401 fill: #00ccff; | |
| 402 stroke: none; | |
| 403 stroke-width: 1px; | |
| 404 } | |
| 405 .key6,.dataPoint6{ | |
| 406 fill: #ff00ff; | |
| 407 stroke: none; | |
| 408 stroke-width: 1px; | |
| 409 } | |
| 410 .key7,.dataPoint7{ | |
| 411 fill: #00ffff; | |
| 412 stroke: none; | |
| 413 stroke-width: 1px; | |
| 414 } | |
| 415 .key8,.dataPoint8{ | |
| 416 fill: #ffff00; | |
| 417 stroke: none; | |
| 418 stroke-width: 1px; | |
| 419 } | |
| 420 .key9,.dataPoint9{ | |
| 421 fill: #cc6666; | |
| 422 stroke: none; | |
| 423 stroke-width: 1px; | |
| 424 } | |
| 425 .key10,.dataPoint10{ | |
| 426 fill: #663399; | |
| 427 stroke: none; | |
| 428 stroke-width: 1px; | |
| 429 } | |
| 430 .key11,.dataPoint11{ | |
| 431 fill: #339900; | |
| 432 stroke: none; | |
| 433 stroke-width: 1px; | |
| 434 } | |
| 435 .key12,.dataPoint12{ | |
| 436 fill: #9966FF; | |
| 437 stroke: none; | |
| 438 stroke-width: 1px; | |
| 439 } | |
| 440 EOL | |
| 441 end | |
| 442 end | |
| 443 end | |
| 444 end |
