Class | PDF::Charts::StdDev |
In: |
lib/pdf/charts/stddev.rb
|
Parent: | Object |
Creates a standard deviation chart. This is a type of chart that is effective for the display of survey results or other data that can easily be measured in terms of the average and the standard deviation from that average.
The scale of responses is the vertical scale; the average data points and standard deviation values are the horizontal scale.
DataPoint | = | Struct.new(:label, :average, :stddev) | A data element. |
bar | [RW] | The standard deviation bar. A line will be drawn through the dot marker (if drawn) from the upper to lower standard deviation. If nil, the line will not be drawn. This is a PDF::Charts::StdDev::Marker object. |
data | [R] | The data used to generate the standard deviation chart. This is an array of DataPoint objects, each containing a label, an average, and the stddev (standard deviation) from that average. |
datapoint_width | [RW] | The width of a single datapoint. |
dot | [RW] | The dot marker. A filled circle will be drawn with this information. If nil, the dot will not be drawn. This is a PDF::Charts::StdDev::Marker object. |
height | [RW] | The height of the chart in PDF user units. Default 200 units. |
inner_borders | [RW] | The inner border style. If nil, no inner borders are drawn. This is a PDF::Charts::StdDev::Marker object. |
label | [RW] | The label style of the labels if they are displayed. This must be a PDF::Charts::StdDev::Label object. |
leading_gap | [RW] | The minimum gap between the chart and the bottom of the page, in PDF user units. |
lower_crossbar | [RW] | The lower crossbar. A line will be drawn across the bottom of the standard deviation bar to the width of the dot marker. If dot is nil, then the line will be twice as wide as it is thick. If nil, the lower crossbar will not be drawn. This is a PDF::Charts::StdDev::Marker object. |
maximum_width | [RW] | The maximum width of the chart in PDF user units. Default 500 units. |
outer_borders | [RW] | The outer border style. If nil, no inner borders are drawn. This is a PDF::Charts::StdDev::Marker object. |
scale | [RW] | The scale of the chart. All values must be within this range. This will be a Scale object. It defaults to a scale of 0..6 with a step of 1. |
show_labels | [RW] | This will be true if labels are to be displayed. |
upper_crossbar | [RW] | The upper crossbar. A line will be drawn across the top of the standard deviation bar to the width of the dot marker. If dot is nil, then the line will be twice as wide as it is thick. If nil, the upper crossbar will not be drawn. This is a PDF::Charts::StdDev::Marker object. |
# File lib/pdf/charts/stddev.rb, line 109 109: def initialize 110: @data = [] 111: 112: @scale = Scale.new do |scale| 113: scale.range = 0..6 114: scale.step = 1 115: scale.style = PDF::Writer::StrokeStyle.new(0.25) 116: scale.show_labels = false 117: scale.label = Label.new do |label| 118: label.text_size = 8 119: label.text_color = Color::RGB::Black 120: label.pad = 2 121: label.decimal_precision = 1 122: end 123: end 124: @leading_gap = 10 125: @show_labels = true 126: @label = Label.new do |label| 127: label.height = 25 128: label.background_color = Color::RGB::Black 129: label.text_color = Color::RGB::White 130: label.text_size = 12 131: end 132: 133: @outer_borders = Marker.new do |marker| 134: marker.style = PDF::Writer::StrokeStyle.new(1.5) 135: marker.color = Color::RGB::Black 136: end 137: @inner_borders = nil 138: 139: @dot = Marker.new do |marker| 140: marker.style = PDF::Writer::StrokeStyle.new(5) 141: marker.color = Color::RGB::Black 142: end 143: @bar = Marker.new do |marker| 144: marker.style = PDF::Writer::StrokeStyle.new(0.5) 145: marker.color = Color::RGB::Black 146: end 147: @upper_crossbar = Marker.new do |marker| 148: marker.style = PDF::Writer::StrokeStyle.new(1) 149: marker.color = Color::RGB::Black 150: end 151: @lower_crossbar = Marker.new do |marker| 152: marker.style = PDF::Writer::StrokeStyle.new(1) 153: marker.color = Color::RGB::Black 154: end 155: 156: @height = 200 157: @maximum_width = 500 158: @datapoint_width = 35 159: 160: yield self if block_given? 161: end
Draw the standard deviation chart on the supplied PDF document.
# File lib/pdf/charts/stddev.rb, line 219 219: def render_on(pdf) 220: raise TypeError, PDF::Writer::Lang[:charts_stddev_data_empty] if @data.empty? 221: data = @data.dup 222: leftover_data = nil 223: 224: loop do 225: # Set up the scale information. 226: scale = [] 227: 228: (@scale.first + @scale.step).step(@scale.last, @scale.step) do |ii| 229: scale << "%01.#{@scale.label.decimal_precision}f" % ii 230: end 231: 232: scales = PDF::Writer::OHash.new 233: scale.each_with_index do |gg, ii| 234: scales[ii] = OpenStruct.new 235: scales[ii].value = gg 236: end 237: 238: # Add information about the scales' locations to the scales 239: # hash. Note that the count is one smaller than it should be, so we're 240: # increasing it. The first scale is the bottom of the chart. 241: scale_count = scale.size + 1 242: 243: label_height_adjuster = 0 244: label_height_adjuster = @label.height if @show_labels 245: 246: chart_area_height = @height - label_height_adjuster 247: scale_height = chart_area_height / scale_count.to_f 248: 249: scales.each_key do |index| 250: this_height = scale_height * (index + 1) + @label.height 251: scales[index].line_height = this_height 252: if @scale.show_labels 253: scales[index].label_height = this_height - 254: (@scale.label.text_size / 3.0) 255: end 256: end 257: 258: # How many sections do we need in this chart, and how wide will it 259: # need to be? 260: chunk_width = @datapoint_width 261: num_chunks = data.size 262: widest_scale_label = 0 263: 264: if @scale.show_labels 265: scales.each_value do |scale| 266: this_width = pdf.text_width(scale.value, @scale.label.text_size) 267: widest_scale_label = this_width if this_width > widest_scale_label 268: end 269: end 270: 271: chart_width = chunk_width * num_chunks 272: total_width = chart_width + widest_scale_label + @scale.label.pad 273: 274: # What happens if the projected width of the chart is too big? 275: # Figure out how to break the chart in pieces. 276: if total_width > @maximum_width 277: max_column_count = 0 278: base_width = widest_scale_label + @scale.label.pad 279: (1..(num_chunks + 1)).each do |ii| 280: if (base_width + (ii * chunk_width)) > @maximum_width 281: break 282: else 283: max_column_count += 1 284: end 285: end 286: 287: leftover_data = data.slice!(max_column_count, -1) 288: 289: num_chunks = data.size 290: chart_width = chunk_width * num_chunks 291: total_width = chart_width + widest_scale_label + @scale.label.pad 292: end 293: 294: chart_y = pdf.y - @height + @leading_gap 295: chart_y += (@outer_borders.style.width * 2.0) if @outer_borders 296: 297: if chart_y < pdf.bottom_margin 298: pdf.start_new_page 299: chart_y = pdf.y - @height 300: chart_y += (@outer_borders.style.width * 2.0) if @outer_borders 301: end 302: 303: chart_x = pdf.absolute_x_middle - (total_width / 2.0) + widest_scale_label 304: 305: # Add labels, if needed. 306: if @show_labels 307: pdf.save_state 308: pdf.fill_color! @label.background_color 309: # Draw a rectangle for each label 310: num_chunks.times do |ii| 311: this_x = chart_x + ii * chunk_width 312: pdf.rectangle(this_x, chart_y, chunk_width, @label.height).fill 313: end 314: 315: # Add a border above the label rectangle. 316: if @outer_borders 317: pdf.stroke_style! @outer_borders.style 318: pdf.line(chart_x, chart_y + @label.height, chart_x + chart_width, chart_y + @label.height).stroke 319: end 320: pdf.fill_color! @label.text_color 321: 322: data.each_with_index do |datum, ii| 323: label = datum.label.to_s 324: label_width = pdf.text_width(label, @label.text_size) 325: this_x = chart_x + (ii * chunk_width) + (chunk_width / 2.0) - (label_width / 2.0) 326: this_y = chart_y + (@label.height / 2.0) - (@label.text_size / 3.0) 327: pdf.add_text(this_x, this_y, label, @label.text_size) 328: end 329: pdf.restore_state 330: end 331: 332: if @inner_borders 333: pdf.save_state 334: pdf.stroke_color! @inner_borders.color 335: pdf.stroke_style! @inner_borders.style 336: (num_chunks - 1).times do |ii| 337: this_x = chart_x + (ii * chunk_width) + chunk_width 338: pdf.line(this_x, chart_y, this_x, chart_y + @height).stroke 339: end 340: pdf.restore_state 341: end 342: 343: pdf.save_state 344: if @outer_borders 345: pdf.stroke_color! @outer_borders.color 346: pdf.stroke_style! @outer_borders.style 347: pdf.rectangle(chart_x, chart_y, chart_width, @height).stroke 348: end 349: 350: if @scale.style 351: pdf.save_state 352: pdf.stroke_style! @scale.style 353: scales.each_value do |scale| 354: this_y = chart_y + scale.line_height 355: pdf.line(chart_x, this_y, chart_x + chart_width, this_y).stroke 356: end 357: pdf.restore_state 358: end 359: 360: if @scale.show_labels 361: pdf.save_state 362: scales.each_value do |scale| 363: this_y = chart_y + scale.label_height 364: label_width = pdf.text_width(scale.value, @scale.label.text_size) 365: this_x = chart_x - label_width - @scale.label.pad 366: pdf.fill_color! @scale.label.text_color 367: pdf.add_text(this_x, this_y, scale.value, @scale.label.text_size) 368: end 369: pdf.restore_state 370: end 371: 372: data.each_with_index do |datum, ii| 373: avg_height = datum.average * scale_height 374: stddev_height = datum.stddev * scale_height 375: this_y = chart_y + label_height_adjuster + avg_height 376: this_x = chart_x + (ii * chunk_width) + (chunk_width / 2.0) 377: line_top_y = this_y + (stddev_height / 2.0) 378: line_bot_y = this_y - (stddev_height / 2.0) 379: 380: # Plot the dot 381: if @dot 382: pdf.stroke_color! @dot.color 383: pdf.stroke_style! @dot.style 384: pdf.circle_at(this_x, this_y, (@dot.style.width / 2.0)).fill 385: end 386: 387: # Plot the bar 388: if @bar 389: pdf.stroke_color! @bar.color 390: pdf.stroke_style! @bar.style 391: pdf.line(this_x, line_top_y, this_x, line_bot_y).stroke 392: end 393: 394: # Plot the crossbars 395: if @upper_crossbar 396: if @dot 397: cb_width = @dot.style.width 398: else 399: cb_width = @upper_crossbar.style.width 400: end 401: pdf.stroke_color! @upper_crossbar.color 402: pdf.stroke_style! @upper_crossbar.style 403: pdf.line(this_x - cb_width, line_top_y, this_x + cb_width, line_top_y).stroke 404: end 405: if @lower_crossbar 406: if @dot 407: cb_width = @dot.style.width 408: else 409: cb_width = @lower_crossbar.style.width 410: end 411: pdf.stroke_color! @lower_crossbar.color 412: pdf.stroke_style! @lower_crossbar.style 413: 414: pdf.line(this_x - cb_width, line_bot_y, this_x + cb_width, line_bot_y).stroke 415: end 416: end 417: 418: pdf.restore_state 419: 420: pdf.y = chart_y 421: 422: break if leftover_data.nil? 423: 424: data = leftover_data 425: leftover_data = nil 426: end 427: 428: pdf.y 429: end