dates2svg 0.0.1.beta1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in dates2svg.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,26 @@
1
+ Copyright (c) 2012-2013. The Board of Trustees of the Leland Stanford Junior University. All rights reserved.
2
+
3
+ Redistribution and use of this distribution in source and binary forms, with or without modification, are permitted provided that: The above copyright notice and this permission notice appear in all copies and supporting documentation; The name, identifiers, and trademarks of The Board of Trustees of the Leland Stanford Junior University are not used in advertising or publicity without the express prior written permission of The Board of Trustees of the Leland Stanford Junior University; Recipients acknowledge that this distribution is made available as a research courtesy, "as is", potentially with defects, without any obligation on the part of The Board of Trustees of the Leland Stanford Junior University to provide support, services, or repair;
4
+
5
+ THE BOARD OF TRUSTEES OF THE LELAND STANFORD JUNIOR UNIVERSITY DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, WITH REGARD TO THIS SOFTWARE, INCLUDING WITHOUT LIMITATION ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, AND IN NO EVENT SHALL THE BOARD OF TRUSTEES OF THE LELAND STANFORD JUNIOR UNIVERSITY BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, TORT (INCLUDING NEGLIGENCE) OR STRICT LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
6
+
7
+ MIT License
8
+
9
+ Permission is hereby granted, free of charge, to any person obtaining
10
+ a copy of this software and associated documentation files (the
11
+ "Software"), to deal in the Software without restriction, including
12
+ without limitation the rights to use, copy, modify, merge, publish,
13
+ distribute, sublicense, and/or sell copies of the Software, and to
14
+ permit persons to whom the Software is furnished to do so, subject to
15
+ the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be
18
+ included in all copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
24
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,29 @@
1
+ # Dates2svg
2
+
3
+ TODO: Write a gem description
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'dates2svg'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install dates2svg
18
+
19
+ ## Usage
20
+
21
+ TODO: Write usage instructions here
22
+
23
+ ## Contributing
24
+
25
+ 1. Fork it
26
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
27
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
28
+ 4. Push to the branch (`git push origin my-new-feature`)
29
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
data/dates2svg.gemspec ADDED
@@ -0,0 +1,24 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'dates2svg/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "dates2svg"
8
+ spec.version = Dates2SVG::VERSION
9
+ spec.authors = ["Jessie Keck"]
10
+ spec.email = ["jessie.keck@gmail.com"]
11
+ spec.description = %q{Generate a SVG month grid w/ heatmap based on array of dates.}
12
+ spec.summary = %q{Turn an array of objects that have a #value method w/ YYYY-MM-DD Date and a #hits method w/ the number of items in that month into an SVG month grid with heatmap.}
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.3"
22
+ spec.add_development_dependency "rake"
23
+ spec.add_development_dependency "rspec"
24
+ end
@@ -0,0 +1,3 @@
1
+ class Dates2SVG
2
+ VERSION = "0.0.1.beta1"
3
+ end
data/lib/dates2svg.rb ADDED
@@ -0,0 +1,194 @@
1
+ require "dates2svg/version"
2
+
3
+ class Dates2SVG
4
+ BOX_SIZE = 15
5
+ BORDER = 1
6
+ BOX_WITH_BORDER = (BOX_SIZE + BORDER)
7
+ def initialize(dates, options={})
8
+ @dates = dates
9
+ @options = options
10
+ 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)
26
+ end
27
+
28
+ def years
29
+ @years ||= []
30
+ end
31
+
32
+ def max
33
+ @max ||= years.map{|year| year.months.map{|month| month.hits} }.flatten.max
34
+ end
35
+
36
+ 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
42
+ end
43
+
44
+ def to_svg
45
+ svg = ""
46
+ # + 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}'>"
48
+ svg << "<g transform='translate(20,35)'>"
49
+ all_years.each_with_index do |year, index|
50
+ selected_years = years.select{|y| y.year.to_i == year.to_i }
51
+ unless selected_years.length == 0
52
+ svg << selected_years.first.to_svg(@options.merge(:index => index, :max => max))
53
+ else
54
+ svg << Dates2SVG::Year.new(year, []).to_svg(@options.merge(:index => index, :max => max))
55
+ end
56
+ end
57
+ svg << calendar_year_listing
58
+ svg << calendar_month_listing
59
+ svg << "</g>"
60
+ svg << "</svg>"
61
+ end
62
+
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
77
+ end
78
+ colors
79
+ end
80
+
81
+ def self.color_options(options={})
82
+ options[:color_options] || ["#EEEEEE", "#330000", "#660000", "#990000", "#CC0000", "#FF0000"]
83
+ end
84
+
85
+ private
86
+
87
+ def all_years
88
+ @options[:year_range] || years.map{|y| y.year }
89
+ end
90
+
91
+ def calendar_year_listing
92
+ svg = ""
93
+ 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>"
95
+ end
96
+ svg
97
+ end
98
+
99
+ def calendar_month_listing
100
+ svg = ""
101
+ ["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>"
103
+ end
104
+ svg
105
+ end
106
+
107
+ class DateWithValue
108
+ attr_reader :year, :month, :day, :hits
109
+ def initialize(ops={})
110
+ @year = ops[:year]
111
+ @month = ops[:month]
112
+ @day = ops[:day]
113
+ @hits = ops[:hits]
114
+ end
115
+ end
116
+
117
+ class Year
118
+ attr_reader :year
119
+ def initialize(year, dates)
120
+ @year = year
121
+ @dates = dates
122
+ parse_dates
123
+ end
124
+
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
+ def months
134
+ @months ||= []
135
+ end
136
+
137
+ def to_svg(options={})
138
+ svg = ""
139
+ svg << "<g transform='translate(#{(BOX_WITH_BORDER * options[:index]) + 12},0)'>"
140
+ all_months.each_with_index do |month, index|
141
+ unless months.select{|m| m.month == month}.length == 0
142
+ svg << months.select{|m| m.month == month}.first.to_svg(options.merge(:index => index))
143
+ else
144
+ svg << Dates2SVG::Month.new(@year, month, []).to_svg(options.merge(:index => index))
145
+ end
146
+ end
147
+ svg << "</g>"
148
+ end
149
+
150
+ private
151
+
152
+ def all_months
153
+ ["01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12"]
154
+ end
155
+
156
+ end
157
+
158
+ class Month
159
+ attr_reader :year, :month
160
+ def initialize(year, month, dates)
161
+ @year = year
162
+ @month = month
163
+ @dates = dates
164
+ end
165
+ def hits
166
+ @hits ||= (@dates.map{|d| d.hits.to_i }.inject(:+) || 0)
167
+ end
168
+
169
+ def to_svg(options={})
170
+ 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)};'>"
172
+ svg << "<title>#{hits} hits on #{@year}-#{@month}</title>"
173
+ svg << "</rect>"
174
+ end
175
+
176
+ private
177
+
178
+ def color(options={})
179
+ max_hits = options[:max] || 3000
180
+ section = (max_hits / 5)
181
+ max = section * 5
182
+
183
+ return Dates2SVG.color_options(options).last if hits > max
184
+ colors = Dates2SVG.color_range(options)
185
+ begin
186
+ # .first turns the hash into an array and .last gets the value
187
+ colors.select{|k,v| k.include?(hits) }.first.last
188
+ rescue
189
+ Dates2SVG.color_options(options).first
190
+ end
191
+ end
192
+ end
193
+
194
+ end
@@ -0,0 +1,130 @@
1
+ require "spec_helper"
2
+ require "dates2svg"
3
+
4
+ class DateValue
5
+ attr_reader :value, :hits
6
+ def initialize(ops={})
7
+ @value = ops[:value]
8
+ @hits = ops[:hits]
9
+ end
10
+ end
11
+
12
+ describe Dates2SVG do
13
+ before(:all) do
14
+ @hits_values = [98, 401, 500, 400, 100]
15
+ @dates = [
16
+ DateValue.new(:value=>"1779-05-02", :hits =>6),
17
+
18
+ DateValue.new(:value=>"1789-01-05", :hits =>8),
19
+ DateValue.new(:value=>"1789-01-11", :hits =>20),
20
+ DateValue.new(:value=>"1789-01-12", :hits =>10),
21
+
22
+ DateValue.new(:value=>"1789-03-10", :hits =>50),
23
+ DateValue.new(:value=>"1789-03-15", :hits =>100),
24
+ DateValue.new(:value=>"1789-03-20", :hits =>400),
25
+ DateValue.new(:value=>"1789-03-20", :hits =>323),
26
+ DateValue.new(:value=>"1789-03-14", :hits =>122),
27
+ DateValue.new(:value=>"1789-03-29", :hits =>426),
28
+
29
+ DateValue.new(:value=>"1789-01-12", :hits =>10),
30
+
31
+ DateValue.new(:value=>"1789-06-30", :hits =>5),
32
+
33
+ DateValue.new(:value=>"1789-07-01", :hits =>7),
34
+ DateValue.new(:value=>"1789-07-02", :hits =>4),
35
+ DateValue.new(:value=>"1789-07-03", :hits =>5),
36
+ DateValue.new(:value=>"1789-07-04", :hits =>7),
37
+ DateValue.new(:value=>"1789-07-06", :hits =>6),
38
+
39
+ DateValue.new(:value=>"1790-06-30", :hits =>40),
40
+
41
+ DateValue.new(:value=>"1790-07-01", :hits =>@hits_values[0]),
42
+ DateValue.new(:value=>"1790-07-02", :hits =>@hits_values[1]),
43
+ DateValue.new(:value=>"1790-07-03", :hits =>@hits_values[2]),
44
+ DateValue.new(:value=>"1790-07-04", :hits =>@hits_values[3]),
45
+ DateValue.new(:value=>"1790-07-06", :hits =>@hits_values[4])]
46
+ @ranges = Dates2SVG.new(@dates)
47
+ end
48
+
49
+ describe "date parsing" do
50
+ describe "years" do
51
+ it "should group the years properly" do
52
+ @ranges.years.length.should_not be 0
53
+ @ranges.years.length.should == 3
54
+ @ranges.years.map{|r| r.year }.should == ["1779", "1789", "1790"]
55
+ end
56
+ end
57
+ describe "months" do
58
+ it "should groups the months properly" do
59
+ year_1789 = @ranges.years.select{|y| y.year == "1789" }.first
60
+ year_1789.months.length.should == 4
61
+ year_1789.months.map{|m| m.month }.should == ["01", "03", "06", "07"]
62
+ end
63
+ it "should sum all the hits in the month" do
64
+ year_1790 = @ranges.years.select{|y| y.year == "1790" }.first
65
+ year_1790.months.length.should > 1
66
+ year_1790.months.select{|m| m.month == "07" }.first.hits.should == @hits_values.inject(:+)
67
+ end
68
+ end
69
+ end
70
+
71
+ describe "max hits" do
72
+ it "should get the maximum number of hits in all months" do
73
+ @ranges.max.should == @hits_values.inject(:+)
74
+ end
75
+ end
76
+
77
+ describe "colors" do
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"
83
+ range.keys.last.should == (2401..3000)
84
+ range[(2401..3000)].should == "#FF0000"
85
+ end
86
+ it "should use the max option passed to provide a differnt color range" do
87
+ range = Dates2SVG.color_range(:max => 6000)
88
+ range.keys.first.should == [0]
89
+ range[[0]].should == "#EEEEEE"
90
+ range.keys.last.should == (4801..6000)
91
+ range[(4801..6000)].should == "#FF0000"
92
+ end
93
+ it "should use an updated color range if one is provided" do
94
+ colors = ["black", "purple", "blue", "green", "yellow", "red"]
95
+ range = Dates2SVG.color_range(:max => 6000, :color_options => colors)
96
+ range.keys.first.should == [0]
97
+ range[[0]].should == colors.first
98
+ range.keys.last.should == (4801..6000)
99
+ range[(4801..6000)].should == colors.last
100
+ end
101
+ end
102
+ end
103
+
104
+ describe "svg export" do
105
+ describe "options" do
106
+ describe "year_range" do
107
+ it "should return only the requested years" do
108
+ @ranges.to_svg.should match(/data-year='1779'/)
109
+ new_range = Dates2SVG.new(@dates, :year_range => (1785..1790))
110
+ new_range.to_svg.should_not match(/data-year='1779'/)
111
+ end
112
+ end
113
+ describe "color_options" do
114
+ it "use the range of color provided" do
115
+ [Dates2SVG.color_options.first, Dates2SVG.color_options.last].each do |color|
116
+ @ranges.to_svg.should match(/fill: #{color};/)
117
+ end
118
+ new_options = ["black", "purple", "blue", "green", "yellow", "red"]
119
+ new_range = Dates2SVG.new(@dates, :color_options => new_options).to_svg
120
+ Dates2SVG.color_options.each do |color|
121
+ new_range.should_not match(/fill: #{color};/)
122
+ end
123
+ [new_options.first, new_options.last].each do |color|
124
+ new_range.should match(/fill: #{color}/)
125
+ end
126
+ end
127
+ end
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,17 @@
1
+ # This file was generated by the `rspec --init` command. Conventionally, all
2
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
3
+ # Require this file using `require "spec_helper"` to ensure that it is only
4
+ # loaded once.
5
+ #
6
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
7
+ RSpec.configure do |config|
8
+ config.treat_symbols_as_metadata_keys_with_true_values = true
9
+ config.run_all_when_everything_filtered = true
10
+ config.filter_run :focus
11
+
12
+ # Run specs in random order to surface order dependencies. If you find an
13
+ # order dependency and want to debug it, you can fix the order by providing
14
+ # the seed, which is printed after each run.
15
+ # --seed 1234
16
+ config.order = 'random'
17
+ end
metadata ADDED
@@ -0,0 +1,109 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dates2svg
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1.beta1
5
+ prerelease: 6
6
+ platform: ruby
7
+ authors:
8
+ - Jessie Keck
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-04-10 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: bundler
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '1.3'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: '1.3'
30
+ - !ruby/object:Gem::Dependency
31
+ name: rake
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: rspec
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ description: Generate a SVG month grid w/ heatmap based on array of dates.
63
+ email:
64
+ - jessie.keck@gmail.com
65
+ executables: []
66
+ extensions: []
67
+ extra_rdoc_files: []
68
+ files:
69
+ - .gitignore
70
+ - .rspec
71
+ - Gemfile
72
+ - LICENSE.txt
73
+ - README.md
74
+ - Rakefile
75
+ - dates2svg.gemspec
76
+ - lib/dates2svg.rb
77
+ - lib/dates2svg/version.rb
78
+ - spec/lib/dates2svg_spec.rb
79
+ - spec/spec_helper.rb
80
+ homepage: ''
81
+ licenses:
82
+ - MIT
83
+ post_install_message:
84
+ rdoc_options: []
85
+ require_paths:
86
+ - lib
87
+ required_ruby_version: !ruby/object:Gem::Requirement
88
+ none: false
89
+ requirements:
90
+ - - ! '>='
91
+ - !ruby/object:Gem::Version
92
+ version: '0'
93
+ required_rubygems_version: !ruby/object:Gem::Requirement
94
+ none: false
95
+ requirements:
96
+ - - ! '>'
97
+ - !ruby/object:Gem::Version
98
+ version: 1.3.1
99
+ requirements: []
100
+ rubyforge_project:
101
+ rubygems_version: 1.8.24
102
+ signing_key:
103
+ specification_version: 3
104
+ summary: ! 'Turn an array of objects that have a #value method w/ YYYY-MM-DD Date
105
+ and a #hits method w/ the number of items in that month into an SVG month grid with
106
+ heatmap.'
107
+ test_files:
108
+ - spec/lib/dates2svg_spec.rb
109
+ - spec/spec_helper.rb