annotate .svn/pristine/7d/7d9750d88bd333d9b7606eb3694629d8e8fa3a5e.svn-base @ 1298:4f746d8966dd redmine_2.3_integration

Merge from redmine-2.3 branch to create new branch redmine-2.3-integration
author Chris Cannam
date Fri, 14 Jun 2013 09:28:30 +0100
parents 622f24f53b42
children
rev   line source
Chris@1295 1 require 'SVG/Graph/Graph'
Chris@1295 2
Chris@1295 3 module SVG
Chris@1295 4 module Graph
Chris@1295 5 # === Create presentation quality SVG pie graphs easily
Chris@1295 6 #
Chris@1295 7 # == Synopsis
Chris@1295 8 #
Chris@1295 9 # require 'SVG/Graph/Pie'
Chris@1295 10 #
Chris@1295 11 # fields = %w(Jan Feb Mar)
Chris@1295 12 # data_sales_02 = [12, 45, 21]
Chris@1295 13 #
Chris@1295 14 # graph = SVG::Graph::Pie.new({
Chris@1295 15 # :height => 500,
Chris@1295 16 # :width => 300,
Chris@1295 17 # :fields => fields,
Chris@1295 18 # })
Chris@1295 19 #
Chris@1295 20 # graph.add_data({
Chris@1295 21 # :data => data_sales_02,
Chris@1295 22 # :title => 'Sales 2002',
Chris@1295 23 # })
Chris@1295 24 #
Chris@1295 25 # print "Content-type: image/svg+xml\r\n\r\n"
Chris@1295 26 # print graph.burn();
Chris@1295 27 #
Chris@1295 28 # == Description
Chris@1295 29 #
Chris@1295 30 # This object aims to allow you to easily create high quality
Chris@1295 31 # SVG pie graphs. You can either use the default style sheet
Chris@1295 32 # or supply your own. Either way there are many options which can
Chris@1295 33 # be configured to give you control over how the graph is
Chris@1295 34 # generated - with or without a key, display percent on pie chart,
Chris@1295 35 # title, subtitle etc.
Chris@1295 36 #
Chris@1295 37 # = Examples
Chris@1295 38 #
Chris@1295 39 # http://www.germane-software/repositories/public/SVG/test/single.rb
Chris@1295 40 #
Chris@1295 41 # == See also
Chris@1295 42 #
Chris@1295 43 # * SVG::Graph::Graph
Chris@1295 44 # * SVG::Graph::BarHorizontal
Chris@1295 45 # * SVG::Graph::Bar
Chris@1295 46 # * SVG::Graph::Line
Chris@1295 47 # * SVG::Graph::Plot
Chris@1295 48 # * SVG::Graph::TimeSeries
Chris@1295 49 #
Chris@1295 50 # == Author
Chris@1295 51 #
Chris@1295 52 # Sean E. Russell <serATgermaneHYPHENsoftwareDOTcom>
Chris@1295 53 #
Chris@1295 54 # Copyright 2004 Sean E. Russell
Chris@1295 55 # This software is available under the Ruby license[LICENSE.txt]
Chris@1295 56 #
Chris@1295 57 class Pie < Graph
Chris@1295 58 # Defaults are those set by Graph::initialize, and
Chris@1295 59 # [show_shadow] true
Chris@1295 60 # [shadow_offset] 10
Chris@1295 61 # [show_data_labels] false
Chris@1295 62 # [show_actual_values] false
Chris@1295 63 # [show_percent] true
Chris@1295 64 # [show_key_data_labels] true
Chris@1295 65 # [show_key_actual_values] true
Chris@1295 66 # [show_key_percent] false
Chris@1295 67 # [expanded] false
Chris@1295 68 # [expand_greatest] false
Chris@1295 69 # [expand_gap] 10
Chris@1295 70 # [show_x_labels] false
Chris@1295 71 # [show_y_labels] false
Chris@1295 72 # [datapoint_font_size] 12
Chris@1295 73 def set_defaults
Chris@1295 74 init_with(
Chris@1295 75 :show_shadow => true,
Chris@1295 76 :shadow_offset => 10,
Chris@1295 77
Chris@1295 78 :show_data_labels => false,
Chris@1295 79 :show_actual_values => false,
Chris@1295 80 :show_percent => true,
Chris@1295 81
Chris@1295 82 :show_key_data_labels => true,
Chris@1295 83 :show_key_actual_values => true,
Chris@1295 84 :show_key_percent => false,
Chris@1295 85
Chris@1295 86 :expanded => false,
Chris@1295 87 :expand_greatest => false,
Chris@1295 88 :expand_gap => 10,
Chris@1295 89
Chris@1295 90 :show_x_labels => false,
Chris@1295 91 :show_y_labels => false,
Chris@1295 92 :datapoint_font_size => 12
Chris@1295 93 )
Chris@1295 94 @data = []
Chris@1295 95 end
Chris@1295 96
Chris@1295 97 # Adds a data set to the graph.
Chris@1295 98 #
Chris@1295 99 # graph.add_data( { :data => [1,2,3,4] } )
Chris@1295 100 #
Chris@1295 101 # Note that the :title is not necessary. If multiple
Chris@1295 102 # data sets are added to the graph, the pie chart will
Chris@1295 103 # display the +sums+ of the data. EG:
Chris@1295 104 #
Chris@1295 105 # graph.add_data( { :data => [1,2,3,4] } )
Chris@1295 106 # graph.add_data( { :data => [2,3,5,9] } )
Chris@1295 107 #
Chris@1295 108 # is the same as:
Chris@1295 109 #
Chris@1295 110 # graph.add_data( { :data => [3,5,8,13] } )
Chris@1295 111 def add_data arg
Chris@1295 112 arg[:data].each_index {|idx|
Chris@1295 113 @data[idx] = 0 unless @data[idx]
Chris@1295 114 @data[idx] += arg[:data][idx]
Chris@1295 115 }
Chris@1295 116 end
Chris@1295 117
Chris@1295 118 # If true, displays a drop shadow for the chart
Chris@1295 119 attr_accessor :show_shadow
Chris@1295 120 # Sets the offset of the shadow from the pie chart
Chris@1295 121 attr_accessor :shadow_offset
Chris@1295 122 # If true, display the data labels on the chart
Chris@1295 123 attr_accessor :show_data_labels
Chris@1295 124 # If true, display the actual field values in the data labels
Chris@1295 125 attr_accessor :show_actual_values
Chris@1295 126 # If true, display the percentage value of each pie wedge in the data
Chris@1295 127 # labels
Chris@1295 128 attr_accessor :show_percent
Chris@1295 129 # If true, display the labels in the key
Chris@1295 130 attr_accessor :show_key_data_labels
Chris@1295 131 # If true, display the actual value of the field in the key
Chris@1295 132 attr_accessor :show_key_actual_values
Chris@1295 133 # If true, display the percentage value of the wedges in the key
Chris@1295 134 attr_accessor :show_key_percent
Chris@1295 135 # If true, "explode" the pie (put space between the wedges)
Chris@1295 136 attr_accessor :expanded
Chris@1295 137 # If true, expand the largest pie wedge
Chris@1295 138 attr_accessor :expand_greatest
Chris@1295 139 # The amount of space between expanded wedges
Chris@1295 140 attr_accessor :expand_gap
Chris@1295 141 # The font size of the data point labels
Chris@1295 142 attr_accessor :datapoint_font_size
Chris@1295 143
Chris@1295 144
Chris@1295 145 protected
Chris@1295 146
Chris@1295 147 def add_defs defs
Chris@1295 148 gradient = defs.add_element( "filter", {
Chris@1295 149 "id"=>"dropshadow",
Chris@1295 150 "width" => "1.2",
Chris@1295 151 "height" => "1.2",
Chris@1295 152 } )
Chris@1295 153 gradient.add_element( "feGaussianBlur", {
Chris@1295 154 "stdDeviation" => "4",
Chris@1295 155 "result" => "blur"
Chris@1295 156 })
Chris@1295 157 end
Chris@1295 158
Chris@1295 159 # We don't need the graph
Chris@1295 160 def draw_graph
Chris@1295 161 end
Chris@1295 162
Chris@1295 163 def get_y_labels
Chris@1295 164 [""]
Chris@1295 165 end
Chris@1295 166
Chris@1295 167 def get_x_labels
Chris@1295 168 [""]
Chris@1295 169 end
Chris@1295 170
Chris@1295 171 def keys
Chris@1295 172 total = 0
Chris@1295 173 max_value = 0
Chris@1295 174 @data.each {|x| total += x }
Chris@1295 175 percent_scale = 100.0 / total
Chris@1295 176 count = -1
Chris@1295 177 a = @config[:fields].collect{ |x|
Chris@1295 178 count += 1
Chris@1295 179 v = @data[count]
Chris@1295 180 perc = show_key_percent ? " "+(v * percent_scale).round.to_s+"%" : ""
Chris@1295 181 x + " [" + v.to_s + "]" + perc
Chris@1295 182 }
Chris@1295 183 end
Chris@1295 184
Chris@1295 185 RADIANS = Math::PI/180
Chris@1295 186
Chris@1295 187 def draw_data
Chris@1295 188 @graph = @root.add_element( "g" )
Chris@1295 189 background = @graph.add_element("g")
Chris@1295 190 midground = @graph.add_element("g")
Chris@1295 191
Chris@1295 192 diameter = @graph_height > @graph_width ? @graph_width : @graph_height
Chris@1295 193 diameter -= expand_gap if expanded or expand_greatest
Chris@1295 194 diameter -= datapoint_font_size if show_data_labels
Chris@1295 195 diameter -= 10 if show_shadow
Chris@1295 196 radius = diameter / 2.0
Chris@1295 197
Chris@1295 198 xoff = (width - diameter) / 2
Chris@1295 199 yoff = (height - @border_bottom - diameter)
Chris@1295 200 yoff -= 10 if show_shadow
Chris@1295 201 @graph.attributes['transform'] = "translate( #{xoff} #{yoff} )"
Chris@1295 202
Chris@1295 203 wedge_text_pad = 5
Chris@1295 204 wedge_text_pad = 20 if show_percent and show_data_labels
Chris@1295 205
Chris@1295 206 total = 0
Chris@1295 207 max_value = 0
Chris@1295 208 @data.each {|x|
Chris@1295 209 max_value = max_value < x ? x : max_value
Chris@1295 210 total += x
Chris@1295 211 }
Chris@1295 212 percent_scale = 100.0 / total
Chris@1295 213
Chris@1295 214 prev_percent = 0
Chris@1295 215 rad_mult = 3.6 * RADIANS
Chris@1295 216 @config[:fields].each_index { |count|
Chris@1295 217 value = @data[count]
Chris@1295 218 percent = percent_scale * value
Chris@1295 219
Chris@1295 220 radians = prev_percent * rad_mult
Chris@1295 221 x_start = radius+(Math.sin(radians) * radius)
Chris@1295 222 y_start = radius-(Math.cos(radians) * radius)
Chris@1295 223 radians = (prev_percent+percent) * rad_mult
Chris@1295 224 x_end = radius+(Math.sin(radians) * radius)
Chris@1295 225 x_end -= 0.00001 if @data.length == 1
Chris@1295 226 y_end = radius-(Math.cos(radians) * radius)
Chris@1295 227 path = "M#{radius},#{radius} L#{x_start},#{y_start} "+
Chris@1295 228 "A#{radius},#{radius} "+
Chris@1295 229 "0, #{percent >= 50 ? '1' : '0'},1, "+
Chris@1295 230 "#{x_end} #{y_end} Z"
Chris@1295 231
Chris@1295 232
Chris@1295 233 wedge = @foreground.add_element( "path", {
Chris@1295 234 "d" => path,
Chris@1295 235 "class" => "fill#{count+1}"
Chris@1295 236 })
Chris@1295 237
Chris@1295 238 translate = nil
Chris@1295 239 tx = 0
Chris@1295 240 ty = 0
Chris@1295 241 half_percent = prev_percent + percent / 2
Chris@1295 242 radians = half_percent * rad_mult
Chris@1295 243
Chris@1295 244 if show_shadow
Chris@1295 245 shadow = background.add_element( "path", {
Chris@1295 246 "d" => path,
Chris@1295 247 "filter" => "url(#dropshadow)",
Chris@1295 248 "style" => "fill: #ccc; stroke: none;"
Chris@1295 249 })
Chris@1295 250 clear = midground.add_element( "path", {
Chris@1295 251 "d" => path,
Chris@1295 252 "style" => "fill: #fff; stroke: none;"
Chris@1295 253 })
Chris@1295 254 end
Chris@1295 255
Chris@1295 256 if expanded or (expand_greatest && value == max_value)
Chris@1295 257 tx = (Math.sin(radians) * expand_gap)
Chris@1295 258 ty = -(Math.cos(radians) * expand_gap)
Chris@1295 259 translate = "translate( #{tx} #{ty} )"
Chris@1295 260 wedge.attributes["transform"] = translate
Chris@1295 261 clear.attributes["transform"] = translate if clear
Chris@1295 262 end
Chris@1295 263
Chris@1295 264 if show_shadow
Chris@1295 265 shadow.attributes["transform"] =
Chris@1295 266 "translate( #{tx+shadow_offset} #{ty+shadow_offset} )"
Chris@1295 267 end
Chris@1295 268
Chris@1295 269 if show_data_labels and value != 0
Chris@1295 270 label = ""
Chris@1295 271 label += @config[:fields][count] if show_key_data_labels
Chris@1295 272 label += " ["+value.to_s+"]" if show_actual_values
Chris@1295 273 label += " "+percent.round.to_s+"%" if show_percent
Chris@1295 274
Chris@1295 275 msr = Math.sin(radians)
Chris@1295 276 mcr = Math.cos(radians)
Chris@1295 277 tx = radius + (msr * radius)
Chris@1295 278 ty = radius -(mcr * radius)
Chris@1295 279
Chris@1295 280 if expanded or (expand_greatest && value == max_value)
Chris@1295 281 tx += (msr * expand_gap)
Chris@1295 282 ty -= (mcr * expand_gap)
Chris@1295 283 end
Chris@1295 284 @foreground.add_element( "text", {
Chris@1295 285 "x" => tx.to_s,
Chris@1295 286 "y" => ty.to_s,
Chris@1295 287 "class" => "dataPointLabel",
Chris@1295 288 "style" => "stroke: #fff; stroke-width: 2;"
Chris@1295 289 }).text = label.to_s
Chris@1295 290 @foreground.add_element( "text", {
Chris@1295 291 "x" => tx.to_s,
Chris@1295 292 "y" => ty.to_s,
Chris@1295 293 "class" => "dataPointLabel",
Chris@1295 294 }).text = label.to_s
Chris@1295 295 end
Chris@1295 296
Chris@1295 297 prev_percent += percent
Chris@1295 298 }
Chris@1295 299 end
Chris@1295 300
Chris@1295 301
Chris@1295 302 def round val, to
Chris@1295 303 up = 10**to.to_f
Chris@1295 304 (val * up).to_i / up
Chris@1295 305 end
Chris@1295 306
Chris@1295 307
Chris@1295 308 def get_css
Chris@1295 309 return <<EOL
Chris@1295 310 .dataPointLabel{
Chris@1295 311 fill: #000000;
Chris@1295 312 text-anchor:middle;
Chris@1295 313 font-size: #{datapoint_font_size}px;
Chris@1295 314 font-family: "Arial", sans-serif;
Chris@1295 315 font-weight: normal;
Chris@1295 316 }
Chris@1295 317
Chris@1295 318 /* key - MUST match fill styles */
Chris@1295 319 .key1,.fill1{
Chris@1295 320 fill: #ff0000;
Chris@1295 321 fill-opacity: 0.7;
Chris@1295 322 stroke: none;
Chris@1295 323 stroke-width: 1px;
Chris@1295 324 }
Chris@1295 325 .key2,.fill2{
Chris@1295 326 fill: #0000ff;
Chris@1295 327 fill-opacity: 0.7;
Chris@1295 328 stroke: none;
Chris@1295 329 stroke-width: 1px;
Chris@1295 330 }
Chris@1295 331 .key3,.fill3{
Chris@1295 332 fill-opacity: 0.7;
Chris@1295 333 fill: #00ff00;
Chris@1295 334 stroke: none;
Chris@1295 335 stroke-width: 1px;
Chris@1295 336 }
Chris@1295 337 .key4,.fill4{
Chris@1295 338 fill-opacity: 0.7;
Chris@1295 339 fill: #ffcc00;
Chris@1295 340 stroke: none;
Chris@1295 341 stroke-width: 1px;
Chris@1295 342 }
Chris@1295 343 .key5,.fill5{
Chris@1295 344 fill-opacity: 0.7;
Chris@1295 345 fill: #00ccff;
Chris@1295 346 stroke: none;
Chris@1295 347 stroke-width: 1px;
Chris@1295 348 }
Chris@1295 349 .key6,.fill6{
Chris@1295 350 fill-opacity: 0.7;
Chris@1295 351 fill: #ff00ff;
Chris@1295 352 stroke: none;
Chris@1295 353 stroke-width: 1px;
Chris@1295 354 }
Chris@1295 355 .key7,.fill7{
Chris@1295 356 fill-opacity: 0.7;
Chris@1295 357 fill: #00ff99;
Chris@1295 358 stroke: none;
Chris@1295 359 stroke-width: 1px;
Chris@1295 360 }
Chris@1295 361 .key8,.fill8{
Chris@1295 362 fill-opacity: 0.7;
Chris@1295 363 fill: #ffff00;
Chris@1295 364 stroke: none;
Chris@1295 365 stroke-width: 1px;
Chris@1295 366 }
Chris@1295 367 .key9,.fill9{
Chris@1295 368 fill-opacity: 0.7;
Chris@1295 369 fill: #cc6666;
Chris@1295 370 stroke: none;
Chris@1295 371 stroke-width: 1px;
Chris@1295 372 }
Chris@1295 373 .key10,.fill10{
Chris@1295 374 fill-opacity: 0.7;
Chris@1295 375 fill: #663399;
Chris@1295 376 stroke: none;
Chris@1295 377 stroke-width: 1px;
Chris@1295 378 }
Chris@1295 379 .key11,.fill11{
Chris@1295 380 fill-opacity: 0.7;
Chris@1295 381 fill: #339900;
Chris@1295 382 stroke: none;
Chris@1295 383 stroke-width: 1px;
Chris@1295 384 }
Chris@1295 385 .key12,.fill12{
Chris@1295 386 fill-opacity: 0.7;
Chris@1295 387 fill: #9966FF;
Chris@1295 388 stroke: none;
Chris@1295 389 stroke-width: 1px;
Chris@1295 390 }
Chris@1295 391 EOL
Chris@1295 392 end
Chris@1295 393 end
Chris@1295 394 end
Chris@1295 395 end