dates2svg 0.0.1.beta1 → 0.0.1.beta2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,5 @@
1
+ notifications:
2
+ email: false
3
+ rvm:
4
+ - 1.9.3
5
+ - 2.0.0
data/README.md CHANGED
@@ -1,6 +1,10 @@
1
+ [![Build Status](https://travis-ci.org/jkeck/dates2svg.png?branch=master)](https://travis-ci.org/jkeck/dates2svg)
2
+
1
3
  # Dates2svg
2
4
 
3
- TODO: Write a gem description
5
+ Turn an array of simple objects including dates and values into an SVG month grid with heat-map.
6
+
7
+ ![date range heat map](http://i.imgur.com/6dcL09C.png)
4
8
 
5
9
  ## Installation
6
10
 
@@ -18,7 +22,31 @@ Or install it yourself as:
18
22
 
19
23
  ## Usage
20
24
 
21
- TODO: Write usage instructions here
25
+ You will need an array of objects that respond to value with a string in a YYYY-MM-DD format (things at the end like timestamps are okay) and respond to hits with a number.
26
+
27
+ dates = [OpenStruct.new(:value => "2013-01-22", :hits => 100),
28
+ OpenStruct.new(:value => "2013-01-05", :hits => 140),
29
+ OpenStruct.new(:value => "2013-01-12", :hits => 10)]
30
+
31
+ Passing that array to Dates2SVG.new will give you your object that you can get the SVG from.
32
+
33
+ svg = Dates2SVG.new(dates).to_svg
34
+
35
+ ### Options
36
+
37
+ There are various options that can be passed in as an options has upon initialization. These options are year_range, color_options, box_size, and border.
38
+
39
+ By default you will only get years that occur in the data passed in the dates array. You can specify a range of years by passing a year_range option.
40
+
41
+ svg = Dates2SVG.new(dates, :year_range => (2000..2020)).to_svg
42
+
43
+ If you would like to change the colors that the grid uses for the heat-map you can specify different colors in the color_options option.
44
+
45
+ svg = Dates2SVG.new(dates, :color_options => ["black", "purple", "blue", "green", "yellow", "red"]).to_svg
46
+
47
+ You can see the current range of colors and hits ranges being used in the SVG by accessing in the color_range key in the options hash. This would be useful in generating a legend.
48
+
49
+ color_range = Dates2SVG.new(dates).options[:color_range]
22
50
 
23
51
  ## Contributing
24
52
 
data/Rakefile CHANGED
@@ -1 +1,5 @@
1
1
  require "bundler/gem_tasks"
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+ task :default => :spec
@@ -1,28 +1,14 @@
1
1
  require "dates2svg/version"
2
2
 
3
3
  class Dates2SVG
4
- BOX_SIZE = 15
5
- BORDER = 1
6
- BOX_WITH_BORDER = (BOX_SIZE + BORDER)
4
+ attr_reader :options
7
5
  def initialize(dates, options={})
8
6
  @dates = dates
9
7
  @options = options
8
+ process_options
10
9
  parse_dates
11
- end
12
-
13
- def parse_dates
14
- @dates.map do |date|
15
- date_from_facet_value(date)
16
- end.sort do |a, b|
17
- a.year.to_i <=> b.year.to_i
18
- end.group_by(&:year).each do |year, dates|
19
- years << Dates2SVG::Year.new(year, dates)
20
- end
21
- end
22
-
23
- def date_from_facet_value(facet)
24
- facet.value[/^(\d{4})-(\d{2})-(\d{2})/]
25
- Dates2SVG::DateWithValue.new(:year => $1, :month => $2, :day => $3, :hits => facet.hits)
10
+ @options[:color_range] = color_range
11
+ @options[:max] = max
26
12
  end
27
13
 
28
14
  def years
@@ -34,24 +20,41 @@ class Dates2SVG
34
20
  end
35
21
 
36
22
  def color_range
37
- self.class.color_range(@options.merge(:max => @max))
38
- end
39
-
40
- def color_options
41
- @options[:color_options] || self.class.color_options
23
+ max_hits = max || 3000
24
+ section = (max_hits) / (@options[:color_options].length - 1)
25
+ colors = {}
26
+ ranges = [[0]]
27
+
28
+ i = 0
29
+ (@options[:color_options].length - 1).times do
30
+ if i == 0
31
+ ranges << (1..(section * 1))
32
+ elsif ((i+1) == (@options[:color_options].length - 1))
33
+ ranges << (((section * i) + 1)..max_hits)
34
+ else
35
+ ranges << (((section * i) + 1)..(section * (i + 1)))
36
+ end
37
+ i = i+1
38
+ end
39
+ @options[:color_options].each_with_index do |color, index|
40
+ unless ranges[index].is_a?(Range) and [ranges[index].first, ranges[index].last].include?(0)
41
+ colors[ranges[index]] = color
42
+ end
43
+ end
44
+ colors
42
45
  end
43
-
46
+
44
47
  def to_svg
45
48
  svg = ""
46
49
  # + 35 and +50 gives us the space for the year month text
47
- svg << "<svg height='#{(12 * BOX_WITH_BORDER) + 35}' width='#{(all_years.to_a.length * BOX_WITH_BORDER) + 50}'>"
50
+ svg << "<svg height='#{(12 * @options[:box_with_border]) + 35}' width='#{(all_years.to_a.length * @options[:box_with_border]) + 50}'>"
48
51
  svg << "<g transform='translate(20,35)'>"
49
52
  all_years.each_with_index do |year, index|
50
53
  selected_years = years.select{|y| y.year.to_i == year.to_i }
51
54
  unless selected_years.length == 0
52
- svg << selected_years.first.to_svg(@options.merge(:index => index, :max => max))
55
+ svg << selected_years.first.to_svg(:index => index)
53
56
  else
54
- svg << Dates2SVG::Year.new(year, []).to_svg(@options.merge(:index => index, :max => max))
57
+ svg << Dates2SVG::Year.new(year, [], @options).to_svg(:index => index)
55
58
  end
56
59
  end
57
60
  svg << calendar_year_listing
@@ -60,30 +63,23 @@ class Dates2SVG
60
63
  svg << "</svg>"
61
64
  end
62
65
 
63
- def self.color_range(options={})
64
- max_hits = options[:max] || 3000
65
- section = (max_hits) / 5
66
- colors = {}
67
- color_options = options[:color_options] || self.color_options
68
- ranges = [[0], (1..(section * 1)),
69
- (((section * 1) + 1)..(section * 2)),
70
- (((section * 2) + 1)..(section * 3)),
71
- (((section * 3) + 1)..(section * 4)),
72
- (((section * 4) + 1)..max_hits)]
73
- color_options.each_with_index do |color, index|
74
- unless ranges[index].is_a?(Range) and [ranges[index].first, ranges[index].last].include?(0)
75
- colors[ranges[index]] = color
76
- end
66
+ private
67
+
68
+ def parse_dates
69
+ @dates.map do |date|
70
+ date_from_facet_value(date)
71
+ end.sort do |a, b|
72
+ a.year.to_i <=> b.year.to_i
73
+ end.group_by(&:year).each do |year, dates|
74
+ years << Dates2SVG::Year.new(year, dates, @options)
77
75
  end
78
- colors
79
76
  end
80
-
81
- def self.color_options(options={})
82
- options[:color_options] || ["#EEEEEE", "#330000", "#660000", "#990000", "#CC0000", "#FF0000"]
77
+
78
+ def date_from_facet_value(facet)
79
+ facet.value[/^(\d{4})-(\d{2})-(\d{2})/]
80
+ Dates2SVG::DateWithValue.new(:year => $1, :month => $2, :day => $3, :hits => facet.hits)
83
81
  end
84
-
85
- private
86
-
82
+
87
83
  def all_years
88
84
  @options[:year_range] || years.map{|y| y.year }
89
85
  end
@@ -91,7 +87,7 @@ class Dates2SVG
91
87
  def calendar_year_listing
92
88
  svg = ""
93
89
  all_years.each_with_index do |year, index|
94
- svg << "<text x='3' y='#{(BOX_WITH_BORDER * index) + 20}' transform='rotate(-90)' class='year'>#{year}</text>"
90
+ svg << "<text x='3' y='#{(@options[:box_with_border] * index) + 20}' transform='rotate(-90)' class='year'>#{year}</text>"
95
91
  end
96
92
  svg
97
93
  end
@@ -99,11 +95,24 @@ class Dates2SVG
99
95
  def calendar_month_listing
100
96
  svg = ""
101
97
  ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", " Oct", "Nov", "Dec"].each_with_index do |month, index|
102
- svg << "<text text-anchor='middle' class='cmonth' dx='0' dy='#{(BOX_WITH_BORDER * index) + 10}'>#{month}</text>"
98
+ svg << "<text text-anchor='middle' class='cmonth' dx='0' dy='#{(@options[:box_with_border] * index) + 10}'>#{month}</text>"
103
99
  end
104
100
  svg
105
101
  end
106
102
 
103
+ def process_options
104
+ @options = self.class.default_options.merge(@options)
105
+ @options[:box_with_border] = (@options[:box_size].to_i + @options[:border].to_i)
106
+ end
107
+
108
+ def self.default_options
109
+ {:color_options => ["#EEEEEE", "#330000", "#660000", "#990000", "#CC0000", "#FF0000"],
110
+ :box_size => 15,
111
+ :border => 1,
112
+ :box_with_border => nil
113
+ }
114
+ end
115
+
107
116
  class DateWithValue
108
117
  attr_reader :year, :month, :day, :hits
109
118
  def initialize(ops={})
@@ -116,32 +125,25 @@ class Dates2SVG
116
125
 
117
126
  class Year
118
127
  attr_reader :year
119
- def initialize(year, dates)
128
+ def initialize(year, dates, options={})
120
129
  @year = year
121
130
  @dates = dates
131
+ @options = options
122
132
  parse_dates
123
133
  end
124
134
 
125
- def parse_dates
126
- @dates.sort do |a, b|
127
- a.month.to_i <=> b.month.to_i
128
- end.group_by(&:month).map do |month, dates|
129
- months << Dates2SVG::Month.new(@year, month, dates)
130
- end
131
- end
132
-
133
135
  def months
134
136
  @months ||= []
135
137
  end
136
138
 
137
139
  def to_svg(options={})
138
140
  svg = ""
139
- svg << "<g transform='translate(#{(BOX_WITH_BORDER * options[:index]) + 12},0)'>"
141
+ svg << "<g transform='translate(#{(@options[:box_with_border] * options[:index]) + 12},0)'>"
140
142
  all_months.each_with_index do |month, index|
141
143
  unless months.select{|m| m.month == month}.length == 0
142
- svg << months.select{|m| m.month == month}.first.to_svg(options.merge(:index => index))
144
+ svg << months.select{|m| m.month == month}.first.to_svg(:index => index)
143
145
  else
144
- svg << Dates2SVG::Month.new(@year, month, []).to_svg(options.merge(:index => index))
146
+ svg << Dates2SVG::Month.new(@year, month, [], @options).to_svg(:index => index)
145
147
  end
146
148
  end
147
149
  svg << "</g>"
@@ -149,6 +151,14 @@ class Dates2SVG
149
151
 
150
152
  private
151
153
 
154
+ def parse_dates
155
+ @dates.sort do |a, b|
156
+ a.month.to_i <=> b.month.to_i
157
+ end.group_by(&:month).map do |month, dates|
158
+ months << Dates2SVG::Month.new(@year, month, dates, @options)
159
+ end
160
+ end
161
+
152
162
  def all_months
153
163
  ["01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12"]
154
164
  end
@@ -157,10 +167,11 @@ class Dates2SVG
157
167
 
158
168
  class Month
159
169
  attr_reader :year, :month
160
- def initialize(year, month, dates)
170
+ def initialize(year, month, dates, options={})
161
171
  @year = year
162
172
  @month = month
163
173
  @dates = dates
174
+ @options = options
164
175
  end
165
176
  def hits
166
177
  @hits ||= (@dates.map{|d| d.hits.to_i }.inject(:+) || 0)
@@ -168,25 +179,25 @@ class Dates2SVG
168
179
 
169
180
  def to_svg(options={})
170
181
  svg = ""
171
- svg << "<rect data-hits='#{hits}' data-year='#{@year}' data-month='#{@month}' class='month' width='#{BOX_SIZE}' height='#{BOX_SIZE}' y='#{BOX_WITH_BORDER * options[:index]}' style='fill: #{color(options)};'>"
182
+ svg << "<rect data-hits='#{hits}' data-year='#{@year}' data-month='#{@month}' class='month' width='#{@options[:box_size]}' height='#{@options[:box_size]}' y='#{@options[:box_with_border] * options[:index]}' style='fill: #{color};'>"
172
183
  svg << "<title>#{hits} hits on #{@year}-#{@month}</title>"
173
184
  svg << "</rect>"
174
185
  end
175
186
 
176
187
  private
177
188
 
178
- def color(options={})
179
- max_hits = options[:max] || 3000
189
+ def color
190
+ max_hits = @options[:max] || 3000
180
191
  section = (max_hits / 5)
181
192
  max = section * 5
182
193
 
183
- return Dates2SVG.color_options(options).last if hits > max
184
- colors = Dates2SVG.color_range(options)
194
+ return @options[:color_options].last if hits > max
195
+ colors = @options[:color_range]
185
196
  begin
186
197
  # .first turns the hash into an array and .last gets the value
187
198
  colors.select{|k,v| k.include?(hits) }.first.last
188
199
  rescue
189
- Dates2SVG.color_options(options).first
200
+ @options[:color_options].first
190
201
  end
191
202
  end
192
203
  end
@@ -1,3 +1,3 @@
1
1
  class Dates2SVG
2
- VERSION = "0.0.1.beta1"
2
+ VERSION = "0.0.1.beta2"
3
3
  end
@@ -76,33 +76,64 @@ describe Dates2SVG do
76
76
 
77
77
  describe "colors" do
78
78
  describe "color_range" do
79
- it "should get the default max_hits when one is not provided" do
80
- range = Dates2SVG.color_range
81
- range.keys.first.should == [0]
82
- range[[0]].should == "#EEEEEE"
79
+ it "should use the default maximum of 3000 if one isn't available (althouth that shouldn't happen)" do
80
+ range = Dates2SVG.new([]).options[:color_range]
83
81
  range.keys.last.should == (2401..3000)
84
82
  range[(2401..3000)].should == "#FF0000"
85
83
  end
86
- it "should use the max option passed to provide a differnt color range" do
87
- range = Dates2SVG.color_range(:max => 6000)
84
+ it "should be from 0 - the max number of hits" do
85
+ range = @ranges.options[:color_range]
88
86
  range.keys.first.should == [0]
89
87
  range[[0]].should == "#EEEEEE"
90
- range.keys.last.should == (4801..6000)
91
- range[(4801..6000)].should == "#FF0000"
88
+ range.keys.last.should == (1197..1499)
89
+ range[(1197..1499)].should == "#FF0000"
92
90
  end
93
91
  it "should use an updated color range if one is provided" do
94
92
  colors = ["black", "purple", "blue", "green", "yellow", "red"]
95
- range = Dates2SVG.color_range(:max => 6000, :color_options => colors)
93
+ range = Dates2SVG.new([], :color_options => colors).options[:color_range]
96
94
  range.keys.first.should == [0]
97
95
  range[[0]].should == colors.first
98
- range.keys.last.should == (4801..6000)
99
- range[(4801..6000)].should == colors.last
96
+ range.keys.last.should == (2401..3000)
97
+ range[(2401..3000)].should == colors.last
98
+ end
99
+ it "should have more range options if additional colors are passed" do
100
+ colors = ["black", "purple", "blue", "green", "yellow", "red"]
101
+ range = Dates2SVG.new([], :color_options => colors).options[:color_range]
102
+ range.keys.length.should == colors.length
103
+ colors << ["#FF0000", "grey"]
104
+ range = Dates2SVG.new([], :color_options => colors).options[:color_range]
105
+ range.keys.length.should == colors.length
100
106
  end
101
107
  end
102
108
  end
103
109
 
104
110
  describe "svg export" do
105
111
  describe "options" do
112
+ describe "box size" do
113
+ it "should default to a 15px box size" do
114
+ @ranges.to_svg.should match(/<rect.*width='15' height='15'.*>/)
115
+ end
116
+ it "should change the box size when a box_size option is available" do
117
+ new_range = Dates2SVG.new(@dates, :box_size => "10").to_svg
118
+ new_range.should_not match(/<rect.*width='15' height='15'.*>/)
119
+ new_range.should match(/<rect.*width='10' height='10'.*>/)
120
+ end
121
+ end
122
+ describe "border" do
123
+ it "should default to a 1px border" do
124
+ @ranges.to_svg.should include "<g transform='translate(28,0)'>"
125
+ end
126
+ it "should change the border when a border options is available" do
127
+ border_2 = Dates2SVG.new(@dates, :border => "2").to_svg
128
+ border_2.should_not include "<g transform='translate(28,0)'>"
129
+ border_2.should include "<g transform='translate(29,0)'>"
130
+
131
+ border_4 = Dates2SVG.new(@dates, :border => "4").to_svg
132
+ border_4.should_not include "<g transform='translate(28,0)'>"
133
+ border_4.should include "<g transform='translate(31,0)'>"
134
+ end
135
+ end
136
+
106
137
  describe "year_range" do
107
138
  it "should return only the requested years" do
108
139
  @ranges.to_svg.should match(/data-year='1779'/)
@@ -112,12 +143,12 @@ describe Dates2SVG do
112
143
  end
113
144
  describe "color_options" do
114
145
  it "use the range of color provided" do
115
- [Dates2SVG.color_options.first, Dates2SVG.color_options.last].each do |color|
146
+ [@ranges.options[:color_options].first, @ranges.options[:color_options].last].each do |color|
116
147
  @ranges.to_svg.should match(/fill: #{color};/)
117
148
  end
118
149
  new_options = ["black", "purple", "blue", "green", "yellow", "red"]
119
150
  new_range = Dates2SVG.new(@dates, :color_options => new_options).to_svg
120
- Dates2SVG.color_options.each do |color|
151
+ @ranges.options[:color_options].each do |color|
121
152
  new_range.should_not match(/fill: #{color};/)
122
153
  end
123
154
  [new_options.first, new_options.last].each do |color|
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dates2svg
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1.beta1
4
+ version: 0.0.1.beta2
5
5
  prerelease: 6
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-04-10 00:00:00.000000000 Z
12
+ date: 2013-04-11 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bundler
@@ -68,6 +68,7 @@ extra_rdoc_files: []
68
68
  files:
69
69
  - .gitignore
70
70
  - .rspec
71
+ - .travis.yml
71
72
  - Gemfile
72
73
  - LICENSE.txt
73
74
  - README.md