annotate lib/SVG/Graph/Line.rb @ 711:60773bbfe050 feature_36_testing

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