dates2svg 0.0.1.beta1 → 0.0.1.beta2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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