Skip to content

Commit

Permalink
Add legend
Browse files Browse the repository at this point in the history
  • Loading branch information
claudiob committed Aug 5, 2015
1 parent 625468c commit cba2f55
Show file tree
Hide file tree
Showing 15 changed files with 193 additions and 25 deletions.
8 changes: 6 additions & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
language: ruby
rvm:
- 2.2.2
notifications:
email: true
matrix:
include:
- rvm: 2.0.0
gemfile: gemfiles/Gemfile.activesupport-3.x
- rvm: 2.2.2
gemfile: gemfiles/Gemfile.activesupport-4.x
4 changes: 4 additions & 0 deletions gemfiles/Gemfile.activesupport-3.x
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
source 'http://rubygems.org'

gem 'activesupport', '~> 3.0'
gemspec path: '../'
4 changes: 4 additions & 0 deletions gemfiles/Gemfile.activesupport-4.x
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
source 'http://rubygems.org'

gem 'activesupport', '~> 4.0'
gemspec path: '../'
3 changes: 3 additions & 0 deletions lib/squid.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
require 'prawn'

require 'active_support/core_ext/string/inflections' # for titleize

require 'squid/config'
require 'squid/interface'

Expand Down
23 changes: 23 additions & 0 deletions lib/squid/base.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
require 'squid/settings'

module Squid
# Abstract class that delegates unhandled calls to a +pdf+ object which
# is convenient when working with Prawn methods
class Base
extend Settings

attr_reader :pdf, :data

def initialize(document, data = {}, settings = {})
@pdf = document
@data = data
@settings = settings
end

# Delegates all unhandled calls to object returned by +pdf+ method.
def method_missing(method, *args, &block)
return super unless pdf.respond_to?(method)
pdf.send method, *args, &block
end
end
end
24 changes: 8 additions & 16 deletions lib/squid/graph.rb
Original file line number Diff line number Diff line change
@@ -1,32 +1,24 @@
require 'squid/settings'
require 'squid/base'
require 'squid/graph/legend'

module Squid
class Graph
extend Settings
class Graph < Base
has_settings :height

attr_reader :pdf

def initialize(document, settings = {})
@pdf = document
@settings = settings
end

# Draws the graph.
def draw
bounding_box [0, cursor], width: bounds.width, height: height do
draw_legend
draw_baseline
end
end

# Delegates all unhandled calls to object returned by +pdf+ method.
def method_missing(method, *args, &block)
return super unless pdf.respond_to?(method)
pdf.send method, *args, &block
end

private

def draw_legend
Legend.new(pdf, data.keys).draw
end

# Draws the baseline of a graph.
# Must run after draw_graph in order to draw the line on top of the graph.
def draw_baseline
Expand Down
77 changes: 77 additions & 0 deletions lib/squid/graph/legend.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
require 'squid/base'

module Squid
# Adds a legend to the graph. The legend is drawn in a floating bounding box
# at the top of the graph, in the right side. The dimensions of the legend
# are fixed: height, width and font size cannot be customized.
# When multiple series are provided, the legend includes all of them, ordered
# from the right to the left.
class Legend < Base

def draw
float do
bounding_box [width, cursor+height*2], width: width, height: height do
right_margin = bounds.right
data.each do |series|
right_margin = draw_label series, right_margin
right_margin = draw_square series, right_margin
right_margin -= label_padding
end
end
end
end

private

# Writes the name of the series, left-aligned, with a small font size.
# @param [Symbol, String] series The series to add to the legend
# @param [Integer] x The current right-margin of the legend
# @return [Integer] The updated right-margin (after adding the label)
def draw_label(series, x)
label = series.to_s.titleize
x -= width_of label, size: font_size
text_box label, at: [x, bounds.top], size: font_size, height: height, valign: :center
x
end

# Draws a square with the same color of the series (next to the label).
# @param [Symbol, String] series The series to add to the legend
# @param [Integer] x The current right-margin of the legend
# @return [Integer] The updated right-margin (after adding the label)
def draw_square(series, x)
x -= square_size + square_padding
fill_rectangle [x, bounds.height - square_size], square_size, square_size
x
end

# Restrict the legend to the right part of the graph
def width
bounds.width/2
end

# Restrict the legend to a specific vertical space
def height
15
end

# Ensure the label fits in the height of the legend
def font_size
height/2
end

# Ensure the square fits in the height of the legend
def square_size
height/3
end

# The horizontal distance left between the labels of two series
def label_padding
height
end

# The horizontal distance left between the squares of two series
def square_padding
height/5
end
end
end
4 changes: 2 additions & 2 deletions lib/squid/interface.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

module Squid
module Interface
def chart(settings = {})
Graph.new(self, settings).draw
def chart(data = {}, settings = {})
Graph.new(self, data, settings).draw
end
end
end
Expand Down
8 changes: 5 additions & 3 deletions manual/squid/height.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@
#
filename = File.basename(__FILE__).gsub('.rb', '.pdf')
Prawn::ManualBuilder::Example.generate(filename) do
data = {views: {2013 => 182, 2014 => 46, 2015 => 102}}

text 'Default height:'
chart
move_down 20
chart data
move_down 30

text 'Custom height:'
chart height: 100
chart data, height: 100
end
15 changes: 15 additions & 0 deletions manual/squid/legend.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# By default, <code>chart</code> adds a legend in the top-right corner of the chart,
# listing the labels of all the series in the graph.
#
filename = File.basename(__FILE__).gsub('.rb', '.pdf')
Prawn::ManualBuilder::Example.generate(filename) do
views = {2013 => 182, 2014 => 46, 2015 => 102}
uniques = {2013 => 110, 2014 => 30, 2015 => 88}

text 'One series:'
chart views: views
move_down 30

text 'Two series:'
chart views: views, uniques: uniques
end
1 change: 1 addition & 0 deletions manual/squid/squid.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

p.section 'Basics' do |s|
s.example 'basic'
s.example 'legend'
end

p.section 'Styling' do |s|
Expand Down
2 changes: 1 addition & 1 deletion spec/graph/height_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
describe 'Graph height', inspect: true do
before do
pdf.stroke_horizontal_rule
pdf.chart options
pdf.chart data, options
pdf.stroke_horizontal_rule
end

Expand Down
36 changes: 36 additions & 0 deletions spec/graph/legend_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
require 'spec_helper'

describe 'Graph legend', inspect: true do
before { pdf.chart data, options }

context 'given no series, does not have any text' do
it { expect(inspected_text.strings).to be_empty }
end

context 'given one series' do
let(:data) { {views: views} }

it 'includes the titleized name of the series' do
expect(inspected_text.strings).to eq ['Views']
end

it 'draws a small square representing the series' do
square = inspected_rectangle.rectangles.first
expect(square[:width]).to be 5.0
expect(square[:height]).to be 5.0
end
end

context 'given two series' do
let(:data) { {views: views, uniques: uniques} }

it 'includes the titleized names of both series' do
expect(inspected_text.strings).to eq ['Views', 'Uniques']
end

it 'prints both names on the same text line' do
lines = inspected_text.positions.map(&:last).uniq
expect(lines).to be_one
end
end
end
6 changes: 6 additions & 0 deletions spec/spec_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,11 @@
let(:pdf) { Prawn::Document.new }
let(:output) { pdf.render }
let(:inspected_line) { PDF::Inspector::Graphics::Line.analyze output }
let(:inspected_text) { PDF::Inspector::Text.analyze output }
let(:inspected_rectangle) { PDF::Inspector::Graphics::Rectangle.analyze output }
let(:data) { {} }
let(:options) { {} }

let(:views) { {2013 => 182, 2014 => 46, 2015 => 102} }
let(:uniques) { {2013 => 110, 2014 => 30, 2015 => 88} }
end
3 changes: 2 additions & 1 deletion squid.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,10 @@ Gem::Specification.new do |spec|
spec.require_paths = ['lib']

spec.add_dependency 'prawn', '~> 2.0'
spec.add_dependency 'activesupport' # 3 or 4, see gemfiles/

spec.add_development_dependency 'pdf-inspector', '~> 1.2'
spec.add_development_dependency 'prawn-manual_builder', '~> 0.2.0'
spec.add_development_dependency 'bundler', '~> 1.9'
spec.add_development_dependency 'rake', '~> 10.0'
spec.add_development_dependency 'rspec', '~> 3.3'
spec.add_development_dependency 'coveralls', '~> 0.8.2'
Expand Down

0 comments on commit cba2f55

Please sign in to comment.