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.
A data element.
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.
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.
The width of a single datapoint.
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.
The height of the chart in PDF user units. Default 200 units.
The inner border style. If nil
, no inner borders are drawn.
This is a PDF::Charts::StdDev::Marker
object.
The label style of the labels if they are displayed. This must be a PDF::Charts::StdDev::Label object.
The minimum gap between the chart and the bottom of the page, in PDF user units.
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.
The maximum width of the chart in PDF user units. Default 500 units.
The outer border style. If nil
, no inner borders are drawn.
This is a PDF::Charts::StdDev::Marker
object.
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.
This will be true
if labels are to be displayed.
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.
Draw the standard deviation chart on the supplied PDF document.
# File lib/pdf/charts/stddev.rb, line 219 def render_on(pdf) raise TypeError, PDF::Writer::Lang[:charts_stddev_data_empty] if @data.empty? data = @data.dup leftover_data = nil loop do # Set up the scale information. scale = [] (@scale.first + @scale.step).step(@scale.last, @scale.step) do |ii| scale << "%01.#{@scale.label.decimal_precision}f" % ii end scales = PDF::Writer::OHash.new scale.each_with_index do |gg, ii| scales[ii] = OpenStruct.new scales[ii].value = gg end # Add information about the scales' locations to the scales # hash. Note that the count is one smaller than it should be, so we're # increasing it. The first scale is the bottom of the chart. scale_count = scale.size + 1 label_height_adjuster = 0 label_height_adjuster = @label.height if @show_labels chart_area_height = @height - label_height_adjuster scale_height = chart_area_height / scale_count.to_f scales.each_key do |index| this_height = scale_height * (index + 1) + @label.height scales[index].line_height = this_height if @scale.show_labels scales[index].label_height = this_height - (@scale.label.text_size / 3.0) end end # How many sections do we need in this chart, and how wide will it # need to be? chunk_width = @datapoint_width num_chunks = data.size widest_scale_label = 0 if @scale.show_labels scales.each_value do |scale| this_width = pdf.text_width(scale.value, @scale.label.text_size) widest_scale_label = this_width if this_width > widest_scale_label end end chart_width = chunk_width * num_chunks total_width = chart_width + widest_scale_label + @scale.label.pad # What happens if the projected width of the chart is too big? # Figure out how to break the chart in pieces. if total_width > @maximum_width max_column_count = 0 base_width = widest_scale_label + @scale.label.pad (1..(num_chunks + 1)).each do |ii| if (base_width + (ii * chunk_width)) > @maximum_width break else max_column_count += 1 end end leftover_data = data.slice!(max_column_count, -1) num_chunks = data.size chart_width = chunk_width * num_chunks total_width = chart_width + widest_scale_label + @scale.label.pad end chart_y = pdf.y - @height + @leading_gap chart_y += (@outer_borders.style.width * 2.0) if @outer_borders if chart_y < pdf.bottom_margin pdf.start_new_page chart_y = pdf.y - @height chart_y += (@outer_borders.style.width * 2.0) if @outer_borders end chart_x = pdf.absolute_x_middle - (total_width / 2.0) + widest_scale_label # Add labels, if needed. if @show_labels pdf.save_state pdf.fill_color! @label.background_color # Draw a rectangle for each label num_chunks.times do |ii| this_x = chart_x + ii * chunk_width pdf.rectangle(this_x, chart_y, chunk_width, @label.height).fill end # Add a border above the label rectangle. if @outer_borders pdf.stroke_style! @outer_borders.style pdf.line(chart_x, chart_y + @label.height, chart_x + chart_width, chart_y + @label.height).stroke end pdf.fill_color! @label.text_color data.each_with_index do |datum, ii| label = datum.label.to_s label_width = pdf.text_width(label, @label.text_size) this_x = chart_x + (ii * chunk_width) + (chunk_width / 2.0) - (label_width / 2.0) this_y = chart_y + (@label.height / 2.0) - (@label.text_size / 3.0) pdf.add_text(this_x, this_y, label, @label.text_size) end pdf.restore_state end if @inner_borders pdf.save_state pdf.stroke_color! @inner_borders.color pdf.stroke_style! @inner_borders.style (num_chunks - 1).times do |ii| this_x = chart_x + (ii * chunk_width) + chunk_width pdf.line(this_x, chart_y, this_x, chart_y + @height).stroke end pdf.restore_state end pdf.save_state if @outer_borders pdf.stroke_color! @outer_borders.color pdf.stroke_style! @outer_borders.style pdf.rectangle(chart_x, chart_y, chart_width, @height).stroke end if @scale.style pdf.save_state pdf.stroke_style! @scale.style scales.each_value do |scale| this_y = chart_y + scale.line_height pdf.line(chart_x, this_y, chart_x + chart_width, this_y).stroke end pdf.restore_state end if @scale.show_labels pdf.save_state scales.each_value do |scale| this_y = chart_y + scale.label_height label_width = pdf.text_width(scale.value, @scale.label.text_size) this_x = chart_x - label_width - @scale.label.pad pdf.fill_color! @scale.label.text_color pdf.add_text(this_x, this_y, scale.value, @scale.label.text_size) end pdf.restore_state end data.each_with_index do |datum, ii| avg_height = datum.average * scale_height stddev_height = datum.stddev * scale_height this_y = chart_y + label_height_adjuster + avg_height this_x = chart_x + (ii * chunk_width) + (chunk_width / 2.0) line_top_y = this_y + (stddev_height / 2.0) line_bot_y = this_y - (stddev_height / 2.0) # Plot the dot if @dot pdf.stroke_color! @dot.color pdf.stroke_style! @dot.style pdf.circle_at(this_x, this_y, (@dot.style.width / 2.0)).fill end # Plot the bar if @bar pdf.stroke_color! @bar.color pdf.stroke_style! @bar.style pdf.line(this_x, line_top_y, this_x, line_bot_y).stroke end # Plot the crossbars if @upper_crossbar if @dot cb_width = @dot.style.width else cb_width = @upper_crossbar.style.width end pdf.stroke_color! @upper_crossbar.color pdf.stroke_style! @upper_crossbar.style pdf.line(this_x - cb_width, line_top_y, this_x + cb_width, line_top_y).stroke end if @lower_crossbar if @dot cb_width = @dot.style.width else cb_width = @lower_crossbar.style.width end pdf.stroke_color! @lower_crossbar.color pdf.stroke_style! @lower_crossbar.style pdf.line(this_x - cb_width, line_bot_y, this_x + cb_width, line_bot_y).stroke end end pdf.restore_state pdf.y = chart_y break if leftover_data.nil? data = leftover_data leftover_data = nil end pdf.y end
# File lib/pdf/charts/stddev.rb, line 109 def initialize @data = [] @scale = Scale.new do |scale| scale.range = 0..6 scale.step = 1 scale.style = PDF::Writer::StrokeStyle.new(0.25) scale.show_labels = false scale.label = Label.new do |label| label.text_size = 8 label.text_color = Color::RGB::Black label.pad = 2 label.decimal_precision = 1 end end @leading_gap = 10 @show_labels = true @label = Label.new do |label| label.height = 25 label.background_color = Color::RGB::Black label.text_color = Color::RGB::White label.text_size = 12 end @outer_borders = Marker.new do |marker| marker.style = PDF::Writer::StrokeStyle.new(1.5) marker.color = Color::RGB::Black end @inner_borders = nil @dot = Marker.new do |marker| marker.style = PDF::Writer::StrokeStyle.new(5) marker.color = Color::RGB::Black end @bar = Marker.new do |marker| marker.style = PDF::Writer::StrokeStyle.new(0.5) marker.color = Color::RGB::Black end @upper_crossbar = Marker.new do |marker| marker.style = PDF::Writer::StrokeStyle.new(1) marker.color = Color::RGB::Black end @lower_crossbar = Marker.new do |marker| marker.style = PDF::Writer::StrokeStyle.new(1) marker.color = Color::RGB::Black end @height = 200 @maximum_width = 500 @datapoint_width = 35 yield self if block_given? end