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.
- data/.travis.yml +5 -0
- data/README.md +30 -2
- data/Rakefile +4 -0
- data/lib/dates2svg.rb +80 -69
- data/lib/dates2svg/version.rb +1 -1
- data/spec/lib/dates2svg_spec.rb +44 -13
- metadata +3 -2
data/.travis.yml
ADDED
data/README.md
CHANGED
@@ -1,6 +1,10 @@
|
|
1
|
+
[](https://travis-ci.org/jkeck/dates2svg)
|
2
|
+
|
1
3
|
# Dates2svg
|
2
4
|
|
3
|
-
|
5
|
+
Turn an array of simple objects including dates and values into an SVG month grid with heat-map.
|
6
|
+
|
7
|
+

|
4
8
|
|
5
9
|
## Installation
|
6
10
|
|
@@ -18,7 +22,31 @@ Or install it yourself as:
|
|
18
22
|
|
19
23
|
## Usage
|
20
24
|
|
21
|
-
|
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
data/lib/dates2svg.rb
CHANGED
@@ -1,28 +1,14 @@
|
|
1
1
|
require "dates2svg/version"
|
2
2
|
|
3
3
|
class Dates2SVG
|
4
|
-
|
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
|
-
|
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
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
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 *
|
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(
|
55
|
+
svg << selected_years.first.to_svg(:index => index)
|
53
56
|
else
|
54
|
-
svg << Dates2SVG::Year.new(year, []).to_svg(
|
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
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
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
|
82
|
-
|
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='#{(
|
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='#{(
|
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(#{(
|
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(
|
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(
|
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='#{
|
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
|
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
|
184
|
-
colors =
|
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
|
-
|
200
|
+
@options[:color_options].first
|
190
201
|
end
|
191
202
|
end
|
192
203
|
end
|
data/lib/dates2svg/version.rb
CHANGED
data/spec/lib/dates2svg_spec.rb
CHANGED
@@ -76,33 +76,64 @@ describe Dates2SVG do
|
|
76
76
|
|
77
77
|
describe "colors" do
|
78
78
|
describe "color_range" do
|
79
|
-
it "should
|
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
|
87
|
-
range =
|
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 == (
|
91
|
-
range[(
|
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.
|
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 == (
|
99
|
-
range[(
|
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
|
-
[
|
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
|
-
|
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.
|
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-
|
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
|