class PDF::Charts::StdDev

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.

Constants

DataPoint

A data element.

VERSION

Attributes

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.

Public Class Methods

new() { |self| ... } click to toggle source
    # File lib/pdf/charts/stddev.rb
110 def initialize
111   @data                       = []
112 
113   @scale                      = Scale.new do |scale|
114     scale.range               = 0..6
115     scale.step                = 1
116     scale.style               = PDF::Writer::StrokeStyle.new(0.25)
117     scale.show_labels         = false
118     scale.label               = Label.new do |label|
119       label.text_size         = 8
120       label.text_color        = Color::RGB::Black
121       label.pad               = 2
122       label.decimal_precision = 1
123     end
124   end
125   @leading_gap              = 10
126   @show_labels              = true
127   @label                    = Label.new do |label|
128     label.height            = 25
129     label.background_color  = Color::RGB::Black
130     label.text_color        = Color::RGB::White
131     label.text_size         = 12
132   end
133 
134   @outer_borders            = Marker.new do |marker|
135     marker.style            = PDF::Writer::StrokeStyle.new(1.5)
136     marker.color            = Color::RGB::Black
137   end
138   @inner_borders            = nil
139 
140   @dot                      = Marker.new do |marker|
141     marker.style            = PDF::Writer::StrokeStyle.new(5)
142     marker.color            = Color::RGB::Black
143   end
144   @bar                      = Marker.new do |marker|
145     marker.style            = PDF::Writer::StrokeStyle.new(0.5)
146     marker.color            = Color::RGB::Black
147   end
148   @upper_crossbar           = Marker.new do |marker|
149     marker.style            = PDF::Writer::StrokeStyle.new(1)
150     marker.color            = Color::RGB::Black
151   end
152   @lower_crossbar           = Marker.new do |marker|
153     marker.style            = PDF::Writer::StrokeStyle.new(1)
154     marker.color            = Color::RGB::Black
155   end
156 
157   @height                   = 200
158   @maximum_width            = 500
159   @datapoint_width          = 35
160 
161   yield self if block_given?
162 end

Public Instance Methods

render_on(pdf) click to toggle source

Draw the standard deviation chart on the supplied PDF document.

    # File lib/pdf/charts/stddev.rb
220 def render_on(pdf)
221   raise TypeError, PDF::Writer::Lang[:charts_stddev_data_empty] if @data.empty?
222   data = @data.dup
223   leftover_data = nil
224 
225   loop do
226     # Set up the scale information.
227     scale = []
228 
229     (@scale.first + @scale.step).step(@scale.last, @scale.step) do |ii|
230       scale << "%01.#{@scale.label.decimal_precision}f" % ii
231     end
232 
233     scales = PDF::Writer::OHash.new
234     scale.each_with_index do |gg, ii|
235       scales[ii] = OpenStruct.new
236       scales[ii].value = gg
237     end
238 
239     # Add information about the scales' locations to the scales
240     # hash. Note that the count is one smaller than it should be, so we're
241     # increasing it. The first scale is the bottom of the chart.
242     scale_count = scale.size + 1
243 
244     label_height_adjuster = 0
245     label_height_adjuster = @label.height if @show_labels
246 
247     chart_area_height = @height - label_height_adjuster
248     scale_height   = chart_area_height / scale_count.to_f
249 
250     scales.each_key do |index|
251       this_height = scale_height * (index + 1) + @label.height
252       scales[index].line_height = this_height
253       if @scale.show_labels
254         scales[index].label_height = this_height -
255         (@scale.label.text_size / 3.0)
256       end
257     end
258 
259     # How many sections do we need in this chart, and how wide will it
260     # need to be?
261     chunk_width = @datapoint_width
262     num_chunks  = data.size
263     widest_scale_label = 0
264 
265     if @scale.show_labels
266       scales.each_value do |scale|
267         this_width = pdf.text_width(scale.value, @scale.label.text_size)
268         widest_scale_label = this_width if this_width > widest_scale_label
269       end
270     end
271 
272     chart_width = chunk_width * num_chunks
273     total_width = chart_width + widest_scale_label + @scale.label.pad
274 
275       # What happens if the projected width of the chart is too big?
276       # Figure out how to break the chart in pieces.
277     if total_width > @maximum_width
278       max_column_count = 0
279       base_width = widest_scale_label + @scale.label.pad
280       (1..(num_chunks + 1)).each do |ii|
281         if (base_width + (ii * chunk_width)) > @maximum_width
282           break
283         else
284           max_column_count += 1
285         end
286       end
287 
288       leftover_data = data.slice!(max_column_count, -1)
289 
290       num_chunks  = data.size
291       chart_width = chunk_width * num_chunks
292       total_width = chart_width + widest_scale_label + @scale.label.pad
293     end
294 
295     chart_y = pdf.y - @height + @leading_gap
296     chart_y += (@outer_borders.style.width * 2.0) if @outer_borders
297 
298     if chart_y < pdf.bottom_margin
299       pdf.start_new_page
300       chart_y = pdf.y - @height
301       chart_y += (@outer_borders.style.width * 2.0) if @outer_borders
302     end
303 
304     chart_x = pdf.absolute_x_middle - (total_width / 2.0) + widest_scale_label
305 
306       # Add labels, if needed.
307     if @show_labels
308       pdf.save_state
309       pdf.fill_color! @label.background_color
310       # Draw a rectangle for each label
311       num_chunks.times do |ii|
312         this_x = chart_x + ii * chunk_width
313         pdf.rectangle(this_x, chart_y, chunk_width, @label.height).fill
314       end
315 
316         # Add a border above the label rectangle.
317       if @outer_borders
318         pdf.stroke_style! @outer_borders.style
319         pdf.line(chart_x, chart_y + @label.height, chart_x + chart_width, chart_y + @label.height).stroke
320       end
321       pdf.fill_color! @label.text_color
322 
323       data.each_with_index do |datum, ii|
324         label = datum.label.to_s
325         label_width = pdf.text_width(label, @label.text_size)
326         this_x = chart_x + (ii * chunk_width) + (chunk_width / 2.0) - (label_width / 2.0)
327         this_y = chart_y + (@label.height / 2.0) - (@label.text_size / 3.0)
328         pdf.add_text(this_x, this_y, label, @label.text_size)
329       end
330       pdf.restore_state
331     end
332 
333     if @inner_borders
334       pdf.save_state
335       pdf.stroke_color! @inner_borders.color
336       pdf.stroke_style! @inner_borders.style
337       (num_chunks - 1).times do |ii|
338         this_x = chart_x + (ii * chunk_width) + chunk_width
339         pdf.line(this_x, chart_y, this_x, chart_y + @height).stroke
340       end
341       pdf.restore_state
342     end
343 
344     pdf.save_state
345     if @outer_borders
346       pdf.stroke_color! @outer_borders.color
347       pdf.stroke_style! @outer_borders.style
348       pdf.rectangle(chart_x, chart_y, chart_width, @height).stroke
349     end
350 
351     if @scale.style
352       pdf.save_state
353       pdf.stroke_style! @scale.style
354       scales.each_value do |scale|
355         this_y = chart_y + scale.line_height
356         pdf.line(chart_x, this_y, chart_x + chart_width, this_y).stroke
357       end
358       pdf.restore_state
359     end
360 
361     if @scale.show_labels
362       pdf.save_state
363       scales.each_value do |scale|
364         this_y = chart_y + scale.label_height
365         label_width = pdf.text_width(scale.value, @scale.label.text_size)
366         this_x = chart_x - label_width - @scale.label.pad
367         pdf.fill_color! @scale.label.text_color
368         pdf.add_text(this_x, this_y, scale.value, @scale.label.text_size)
369       end
370       pdf.restore_state
371     end
372 
373     data.each_with_index do |datum, ii|
374       avg_height    = datum.average * scale_height
375       stddev_height = datum.stddev * scale_height
376       this_y        = chart_y + label_height_adjuster + avg_height
377       this_x        = chart_x + (ii * chunk_width) + (chunk_width / 2.0)
378       line_top_y    = this_y + (stddev_height / 2.0)
379       line_bot_y    = this_y - (stddev_height / 2.0)
380 
381         # Plot the dot
382       if @dot
383         pdf.stroke_color! @dot.color
384         pdf.stroke_style! @dot.style
385         pdf.circle_at(this_x, this_y, (@dot.style.width / 2.0)).fill
386       end
387 
388         # Plot the bar
389       if @bar
390         pdf.stroke_color! @bar.color
391         pdf.stroke_style! @bar.style
392         pdf.line(this_x, line_top_y, this_x, line_bot_y).stroke
393       end
394 
395         # Plot the crossbars
396       if @upper_crossbar
397         if @dot
398           cb_width = @dot.style.width
399         else
400           cb_width = @upper_crossbar.style.width
401         end
402         pdf.stroke_color! @upper_crossbar.color
403         pdf.stroke_style! @upper_crossbar.style
404         pdf.line(this_x - cb_width, line_top_y, this_x + cb_width, line_top_y).stroke
405       end
406       if @lower_crossbar
407         if @dot
408           cb_width = @dot.style.width
409         else
410           cb_width = @lower_crossbar.style.width
411         end
412         pdf.stroke_color! @lower_crossbar.color
413         pdf.stroke_style! @lower_crossbar.style
414 
415         pdf.line(this_x - cb_width, line_bot_y, this_x + cb_width, line_bot_y).stroke
416       end
417     end
418 
419     pdf.restore_state
420 
421     pdf.y = chart_y
422 
423     break if leftover_data.nil?
424 
425     data = leftover_data
426     leftover_data = nil
427   end
428 
429   pdf.y
430 end