class Squid::Plotter

A Plotter wraps a Prawn::Document object in order to provide new methods like `gridline` or `ticks` used by Squid::Graph to plot graph elements.

Attributes

paddings[RW]

Public Class Methods

new(pdf, bottom:) click to toggle source

@param [Prawn::Document] a PDF document to wrap in a Plotter instance.

# File lib/squid/plotter.rb, line 10
def initialize(pdf, bottom:)
  @pdf = pdf
  @bottom = bottom
end

Public Instance Methods

axis_labels(labels) click to toggle source
# File lib/squid/plotter.rb, line 56
def axis_labels(labels)
  labels.each do |label|
    x = (label.align == :right) ? 0 : @pdf.bounds.right - label.width
    y = label.y + @bottom + text_options[:height] / 2
    options = text_options.merge width: label.width, at: [x, y]
    @pdf.text_box label.label, options.merge(align: label.align)
  end
end
box(x: 0, y: @pdf.cursor, w: @pdf.bounds.width, h:, border: false) { || ... } click to toggle source

Draws a bounding box of the given height, rendering the block inside it.

# File lib/squid/plotter.rb, line 16
def box(x: 0, y: @pdf.cursor, w: @pdf.bounds.width, h:, border: false)
  @pdf.bounding_box [x, y], width: w, height: h do
    @pdf.stroke_bounds if border
    yield
  end
end
categories(labels, every:, ticks:) click to toggle source
# File lib/squid/plotter.rb, line 65
def categories(labels, every:, ticks:)
  labels.each.with_index do |label, index|
    w = width / labels.count.to_f
    x = left + w * (index)
    padding = 2
    options = category_options.merge(width: every*w-2*padding, at: [x+padding-w*(every/2.0-0.5), @bottom])
    @pdf.text_box label, options if (index % every).zero?
    @pdf.stroke_vertical_line @bottom, @bottom - 2, at: x + w/2 if ticks
  end
end
columns(series, options = {}) click to toggle source
# File lib/squid/plotter.rb, line 103
def columns(series, options = {})
  items(series, options.merge(fill: true, count: series.size)) do |point, w, i, padding|
    item_w = (w - 2 * padding)/ series.size
    x, y = point.index*w + padding + left + i*item_w, point.y + @bottom
    @pdf.fill_rectangle [x, y], item_w, point.height
  end
end
horizontal_line(y, options = {}) click to toggle source

Draws a horizontal line.

# File lib/squid/plotter.rb, line 45
def horizontal_line(y, options = {})
  with options do
    at = y + @bottom
    @pdf.stroke_horizontal_line left, @pdf.bounds.right - right, at: at
  end
end
legend(labels, height:, right: 0, colors: []) click to toggle source

Draws the graph legend with the given labels. @param [Array<LegendItem>] The labels to write as part of the legend.

# File lib/squid/plotter.rb, line 25
def legend(labels, height:, right: 0, colors: [])
  left = @pdf.bounds.width/2
  box(x: left, y: @pdf.bounds.top, w: left, h: height) do
    x = @pdf.bounds.right - right
    options = {size: 7, height: @pdf.bounds.height, valign: :center}
    labels.each.with_index do |label, i|
      index = labels.size - 1 - i
      series_color = colors.fetch index, series_colors(index)
      color = Array.wrap(series_color).first
      x = legend_item label, x, color, options
    end
  end
end
lines(series, options = {}) click to toggle source
# File lib/squid/plotter.rb, line 83
def lines(series, options = {})
  x, y = nil, nil
  line_widths = options.delete(:line_widths) { [] }
  items(series, options) do |point, w, i, padding|
    prev_x, prev_y = x, y
    x, y = (point.index + 0.5)*w + left, point.y + @bottom
    line_width = line_widths.fetch i, 3
    with line_width: line_width, cap_style: :round do
      @pdf.line [prev_x, prev_y], [x,y] unless point.index.zero? || prev_y.nil? || prev_x > x
    end
  end
end
points(series, options = {}) click to toggle source
# File lib/squid/plotter.rb, line 76
def points(series, options = {})
  items(series, options) do |point, w, i, padding|
    x, y = (point.index + 0.5)*w + left, point.y + @bottom
    @pdf.fill_circle [x, y], 5
  end
end
series_colors(index) click to toggle source
# File lib/squid/plotter.rb, line 39
def series_colors(index)
  default_colors = %w(2e578c 5d9648 e7a13d bc2d30 6f3d79 7d807f)
  default_colors.fetch(index % default_colors.size)
end
stacks(series, options = {}) click to toggle source
# File lib/squid/plotter.rb, line 96
def stacks(series, options = {})
  items(series, options.merge(fill: true)) do |point, w, i, padding|
    x, y = point.index*w + padding + left, point.y + @bottom
    @pdf.fill_rectangle [x, y], w - 2*padding, point.height
  end
end
width_of(label) click to toggle source
# File lib/squid/plotter.rb, line 52
def width_of(label)
  @pdf.width_of(label, size: 8).ceil
end

Private Instance Methods

category_options() click to toggle source
# File lib/squid/plotter.rb, line 125
def category_options
  text_options.merge align: :center, leading: -3, disable_wrap_by_char: true
end
item(point, color, w, fill, index, count) { |point, w, index, padding| ... } click to toggle source
# File lib/squid/plotter.rb, line 151
def item(point, color, w, fill, index, count)
  padding = w / 8

  with transparency: 0.95, fill_color: color, stroke_color: color do
    yield point, w, index, padding
  end

  with fill_color: (point.negative && fill ? 'ffffff' : color) do
    options = [{size: 10, styles: [:bold], text: point.label}]
    position = {align: :center, valign: :bottom, height: 20}
    position[:width] = (w - 2*padding) / count
    x = left + point.index*w + padding
    x += index * position[:width] if count > 1
    position[:at] = [x, point.y + @bottom + 24]
    @pdf.formatted_text_box options, position
  end if point.label
end
items(series, colors: [], fill: false, count: 1, starting_at: 0, &block) click to toggle source
# File lib/squid/plotter.rb, line 138
def items(series, colors: [], fill: false, count: 1, starting_at: 0, &block)
  series.reverse_each.with_index do |points, reverse_index|
    index = series.size - reverse_index - 1
    color_index = index + starting_at
    w = width / points.size.to_f
    series_color = colors.fetch color_index, series_colors(color_index)
    item_color = Array.wrap(series_color).cycle
    points.select(&:y).each do |point|
      item point, item_color.next, w, fill, index, count, &block
    end
  end
end
left() click to toggle source
# File lib/squid/plotter.rb, line 113
def left
  @paddings[:left].zero? ? 0 : @paddings[:left] + 5
end
legend_item(label, x, color, options) click to toggle source

Draws a single item of the legend, which includes the label and the symbol with the matching color. Labels are written from right to left. @param

# File lib/squid/plotter.rb, line 172
def legend_item(label, x, color, options)
  size, symbol_padding, entry_padding = 5, 3, 12
  x -= @pdf.width_of(label, size: 7).ceil
  @pdf.text_box label, options.merge(at: [x, @pdf.bounds.height])
  x -= (symbol_padding + size)
  with fill_color: color do
    @pdf.fill_rectangle [x, @pdf.bounds.height - size], size, size
  end
  x - entry_padding
end
right() click to toggle source
# File lib/squid/plotter.rb, line 117
def right
  @paddings[:right].zero? ? 0 : @paddings[:right] + 5
end
text_options() click to toggle source
# File lib/squid/plotter.rb, line 129
def text_options
  options = {}
  options[:height] = 20
  options[:size] = 8
  options[:valign] = :center
  options[:overflow] = :shrink_to_fit
  options
end
width() click to toggle source
# File lib/squid/plotter.rb, line 121
def width
  @pdf.bounds.width - left - right
end
with(new_values = {}) { || ... } click to toggle source

Convenience method to wrap a block by setting and unsetting a Prawn property such as line_width.

# File lib/squid/plotter.rb, line 185
def with(new_values = {})
  transparency = new_values.delete(:transparency) { 1.0 }
  old_values = Hash[new_values.map{|k,_| [k,@pdf.public_send(k)]}]
  new_values.each{|k, new_value| @pdf.public_send "#{k}=", new_value }
  @pdf.transparent(transparency) do
    @pdf.stroke { yield }
  end
  old_values.each{|k, old_value| @pdf.public_send "#{k}=", old_value }
end