analytics_charts 0.0.1
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.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +44 -0
- data/Rakefile +1 -0
- data/analytics_charts.gemspec +27 -0
- data/appendix.md +20 -0
- data/lib/analytics_charts/version.rb +3 -0
- data/lib/custom_pie.rb +211 -0
- metadata +99 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 239faf9fca54620fbff0cfac86d9f53e7b83b371
|
4
|
+
data.tar.gz: 639fa9f5fa16c0999fbec645c1587e2f94514bdd
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 77b264d681853bf1c30a5996365b3e326956e0a29bb0926a3aa4cb6caa5947dc0e92202ee61ed7316b6022c61ee592a9ea66b3c1003570e9fea138c624a2c848
|
7
|
+
data.tar.gz: 7b7a7a3e50b27f3bc1bd2aa97c82a80c2ab5c2d828b9fcb67f3a5d34f4a44f19e96d77a58baab2ebb54d5eae4e3ae5c7f6d30d831fba571d300066ab6addce69
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2015 STEPHEN YU
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
# AnalyticsCharts
|
2
|
+
|
3
|
+
Custom library for a pie chart. Originated from the gruff library, but branched out.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'analytics_charts'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install analytics_charts
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
#!/usr/bin/env ruby
|
22
|
+
require 'analytics-charts'
|
23
|
+
g = AnalyticsCharts::CustomPie.new('./yourImage.png')
|
24
|
+
hash = {
|
25
|
+
'fill' => 'blue',
|
26
|
+
'font_family' => 'Helvetica',
|
27
|
+
'pointsize' => 64,
|
28
|
+
'font_weight' => 700
|
29
|
+
}
|
30
|
+
|
31
|
+
g.set_pie_geometry(1242,620, 250)
|
32
|
+
g.insert_pie_data("ndaf me3", 15.1,1)
|
33
|
+
g.insert_pie_data("ndaf me4", 15,3)
|
34
|
+
g.insert_pie_data("ndaf me2", 15.7,2)
|
35
|
+
g.insert_pie_data("ndaf me1", 300.22,0)
|
36
|
+
g.write
|
37
|
+
|
38
|
+
## Contributing
|
39
|
+
|
40
|
+
1. Fork it ( http://github.com/tryceattack/analytics_charts/fork )
|
41
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
42
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
43
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
44
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'analytics_charts/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "analytics_charts"
|
8
|
+
spec.version = AnalyticsCharts::VERSION
|
9
|
+
spec.authors = ["STEPHEN YU"]
|
10
|
+
spec.email = ["istephenyu@gmail.com"]
|
11
|
+
spec.summary = %q{Chart for analytics_charts}
|
12
|
+
spec.description = %q{Initially wrote code on top of gruff library. But eventually to do
|
13
|
+
more fined-tuned data rendering and the need for more lightweight code, I branched out to
|
14
|
+
this new library. Source for original code of gruff library.
|
15
|
+
https://github.com/topfunky/gruff/}
|
16
|
+
spec.homepage = ""
|
17
|
+
spec.license = "MIT"
|
18
|
+
|
19
|
+
spec.files = `git ls-files -z`.split("\x0")
|
20
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
21
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
22
|
+
spec.require_paths = ["lib"]
|
23
|
+
|
24
|
+
spec.add_development_dependency "bundler", "~> 1.5"
|
25
|
+
spec.add_development_dependency "rake"
|
26
|
+
spec.add_dependency 'rmagick'
|
27
|
+
end
|
data/appendix.md
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
if small_slice_index[0] == 1 and small_slice_index[1] == 1
|
2
|
+
label_offset_degrees[0] = -15
|
3
|
+
label_offset_degrees[1] = 15
|
4
|
+
label_offset_degrees[2] = 15
|
5
|
+
label_offset_degrees[3] = -15
|
6
|
+
elsif small_slice_index[1] == 1 and small_slice_index[2] == 1
|
7
|
+
label_offset_degrees[0] = -15
|
8
|
+
label_offset_degrees[1] = -15
|
9
|
+
label_offset_degrees[2] = 15
|
10
|
+
label_offset_degrees[3] = 15
|
11
|
+
elsif small_slice_index[2] == 1 and small_slice_index[3] == 1
|
12
|
+
label_offset_degrees[0] = 15
|
13
|
+
label_offset_degrees[1] = -15
|
14
|
+
label_offset_degrees[2] = -15
|
15
|
+
label_offset_degrees[3] = 15
|
16
|
+
elsif small_slice_index[3] == 1 and small_slice_index[0] == 1
|
17
|
+
label_offset_degrees[0] = 15
|
18
|
+
label_offset_degrees[1] = 15
|
19
|
+
label_offset_degrees[2] = -15
|
20
|
+
label_offset_degrees[3] = -15
|
data/lib/custom_pie.rb
ADDED
@@ -0,0 +1,211 @@
|
|
1
|
+
require 'RMagick'
|
2
|
+
|
3
|
+
class AnalyticsCharts::CustomPie
|
4
|
+
include Magick
|
5
|
+
attr_accessor :pie_center_x
|
6
|
+
attr_accessor :pie_center_y
|
7
|
+
attr_accessor :pie_radius
|
8
|
+
|
9
|
+
def initialize(image_path)
|
10
|
+
@base_image = Image.read(image_path)[0]
|
11
|
+
@d = Draw.new
|
12
|
+
@data = Hash.new # Value is array with two items
|
13
|
+
@aggregate = Array([0,0,0,0]) # Cluster brands into categories
|
14
|
+
@columns = @base_image.columns
|
15
|
+
@rows = @base_image.rows
|
16
|
+
set_pie_colors(%w(#AD1F25 #BE6428 #C1B630 #1E753B))
|
17
|
+
end
|
18
|
+
|
19
|
+
def set_pie_geometry(x, y, radius)
|
20
|
+
@pie_center_x = x
|
21
|
+
@pie_center_y = y
|
22
|
+
@pie_radius = radius
|
23
|
+
end
|
24
|
+
|
25
|
+
def set_pie_colors(list)
|
26
|
+
@colors = list
|
27
|
+
end
|
28
|
+
|
29
|
+
def insert_pie_data(name, amount, quality)
|
30
|
+
#convert all '' instances to an apostrophe
|
31
|
+
name = name.gsub(/'/, "\'")
|
32
|
+
@data[name] = [amount, quality]
|
33
|
+
@aggregate[quality] += amount
|
34
|
+
end
|
35
|
+
|
36
|
+
def insert_text(x_offset, y_offset, text, features = {})
|
37
|
+
features.each { |feature, attribute|
|
38
|
+
set_feature(feature, attribute)
|
39
|
+
}
|
40
|
+
#puts @d.get_type_metrics(@base_image, text.to_s).width,@d.get_type_metrics(@base_image, text.to_s).height,text
|
41
|
+
@d.annotate(@base_image, 0 ,0, x_offset, y_offset, text)
|
42
|
+
end
|
43
|
+
|
44
|
+
def draw
|
45
|
+
if @data.size > 0
|
46
|
+
@d = Draw.new
|
47
|
+
@d.stroke_width(@pie_radius)
|
48
|
+
total_sum = 0.0
|
49
|
+
prev_degrees = 60.0
|
50
|
+
@d.fill_opacity(0) # VERY IMPORTANT, otherwise undesired artifact can result.
|
51
|
+
total_sum = @aggregate.inject(:+) + 0.0 # Sum elements and make it a float
|
52
|
+
degrees = Array([0,0,0,0])
|
53
|
+
label_offset_degrees = Array([0,0,0,0])
|
54
|
+
@aggregate.each_with_index do |data_row, index|
|
55
|
+
degrees[index] = (data_row / total_sum) * 360.0
|
56
|
+
end
|
57
|
+
num_small_slices = 0
|
58
|
+
small_slice_index = Array([0,0,0,0])
|
59
|
+
for i in 0..3
|
60
|
+
if degrees[i] != 0 and degrees[i] < 18.0
|
61
|
+
num_small_slices += 1
|
62
|
+
small_slice_index[i] = 1
|
63
|
+
end
|
64
|
+
end
|
65
|
+
for i in 0..3 # First draw slices
|
66
|
+
next if degrees[i] == 0
|
67
|
+
@d = @d.stroke @colors[i]
|
68
|
+
# ellipse will draw the the stroke centered on the first two parameters offset by the second two.
|
69
|
+
# therefore, in order to draw a circle of the proper diameter we must center the stroke at
|
70
|
+
# half the radius for both x and y
|
71
|
+
@d = @d.ellipse(@pie_center_x, @pie_center_y,
|
72
|
+
@pie_radius / 2.0, @pie_radius / 2.0,
|
73
|
+
prev_degrees, prev_degrees + degrees[i] + 0.5) # <= +0.5 'fudge factor' gets rid of the ugly gaps
|
74
|
+
prev_degrees += degrees[i]
|
75
|
+
end
|
76
|
+
# If less than two small slices, or there are two small slices that are not adjacent
|
77
|
+
if num_small_slices < 2 or (num_small_slices == 2 and small_slice_index[0] == small_slice_index[2])
|
78
|
+
#Do nothing
|
79
|
+
# If two adjacent small slices, push them apart. Non-adjacent case is taken care of above.
|
80
|
+
# I also push back the other labels too. The logic is condensed. To see original logic,
|
81
|
+
# consult appendix.html
|
82
|
+
elsif num_small_slices == 2
|
83
|
+
if small_slice_index[1] == 1
|
84
|
+
label_offset_degrees[0] = -15
|
85
|
+
label_offset_degrees[2] = 15
|
86
|
+
else
|
87
|
+
label_offset_degrees[0] = 15
|
88
|
+
label_offset_degrees[2] = -15
|
89
|
+
end
|
90
|
+
if small_slice_index[2] == 1
|
91
|
+
label_offset_degrees[1] = -15
|
92
|
+
label_offset_degrees[3] = 15
|
93
|
+
else
|
94
|
+
label_offset_degrees[1] = 15
|
95
|
+
label_offset_degrees[3] = -15
|
96
|
+
end
|
97
|
+
# In this case, push apart only the outside small slices.
|
98
|
+
elsif num_small_slices == 3
|
99
|
+
if small_slice_index[0] == 0
|
100
|
+
label_offset_degrees[1] = -15
|
101
|
+
label_offset_degrees[3] = 15
|
102
|
+
elsif small_slice_index[1] == 0
|
103
|
+
label_offset_degrees[2] = -15
|
104
|
+
label_offset_degrees[0] = 15
|
105
|
+
elsif small_slice_index[2] == 0
|
106
|
+
label_offset_degrees[3] = -15
|
107
|
+
label_offset_degrees[1] = 15
|
108
|
+
elsif small_slice_index[3] == 0
|
109
|
+
label_offset_degrees[0] = -15
|
110
|
+
label_offset_degrees[2] = 15
|
111
|
+
end
|
112
|
+
end
|
113
|
+
prev_degrees = 60.0 # Now focus on labels
|
114
|
+
@aggregate.each_with_index do |data_row, i|
|
115
|
+
next if degrees[i] == 0
|
116
|
+
half_angle = prev_degrees + degrees[i] / 2
|
117
|
+
label_string = '$' + data_row.to_s
|
118
|
+
draw_pie_label(@pie_center_x,@pie_center_y, half_angle + label_offset_degrees[i],
|
119
|
+
@pie_radius, label_string)
|
120
|
+
prev_degrees += degrees[i]
|
121
|
+
end
|
122
|
+
@d.draw(@base_image)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def write(filename='graph.png')
|
127
|
+
draw
|
128
|
+
draw_labels
|
129
|
+
@base_image.write(filename)
|
130
|
+
end
|
131
|
+
|
132
|
+
def draw_labels
|
133
|
+
@d.pointsize = 56
|
134
|
+
@d.align = LeftAlign
|
135
|
+
sorted_data = @data.sort_by{|key,value| -value[1]} # Sort by descending quality
|
136
|
+
x_offset = 180 #HARD CODED, fix later
|
137
|
+
y_offset = 440 #HARD CODED
|
138
|
+
for data in sorted_data
|
139
|
+
if data[1][0] > 0 # Amount > 0
|
140
|
+
font_weight = 900 # Very Bold
|
141
|
+
text = data[0] + ' <--'
|
142
|
+
else
|
143
|
+
text = data[0]
|
144
|
+
font_weight = 900 # Normal
|
145
|
+
end
|
146
|
+
case data[1][1]
|
147
|
+
when 3
|
148
|
+
insert_text(x_offset, y_offset, text, {'fill'=> '#1E753B', 'font_weight'=> font_weight })
|
149
|
+
when 2
|
150
|
+
insert_text(x_offset, y_offset, text, {'fill'=> '#C1B630', 'font_weight'=> font_weight })
|
151
|
+
when 1
|
152
|
+
insert_text(x_offset, y_offset, text, {'fill'=> '#BE6428', 'font_weight'=> font_weight })
|
153
|
+
when 0
|
154
|
+
insert_text(x_offset, y_offset, text, {'fill'=> '#AD1F25', 'font_weight'=> font_weight })
|
155
|
+
end
|
156
|
+
y_offset += 60 #HARD CODED, fix later
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
def draw_pie_label(center_x, center_y, angle, radius, percent)
|
161
|
+
@d.pointsize = 56 #HARD CODED, fix later
|
162
|
+
width = @d.get_type_metrics(@base_image, percent.to_s).width
|
163
|
+
ascent = @d.get_type_metrics(@base_image, percent.to_s).ascent
|
164
|
+
descent = @d.get_type_metrics(@base_image, percent.to_s).descent
|
165
|
+
radians = angle * Math::PI / 180.0
|
166
|
+
x = center_x + radius * Math.cos(radians)
|
167
|
+
# By default, text is centered at bottom, so need to shift vertically to center it
|
168
|
+
y = center_y + ascent / 2.0 + radius * Math.sin(radians)
|
169
|
+
# Imagine the text box around the text
|
170
|
+
# Shift text box so a corner is tangent to circle
|
171
|
+
if x > center_x
|
172
|
+
x += width / 2.0
|
173
|
+
end
|
174
|
+
if x < center_x
|
175
|
+
x -= width / 2.0
|
176
|
+
end
|
177
|
+
if y > center_y
|
178
|
+
y += ascent / 2.0
|
179
|
+
end
|
180
|
+
if y < center_y
|
181
|
+
y -= (ascent / 2.0 - descent) # descent to account for '$' descent,
|
182
|
+
# descent value retrieved is negative, so sub instead of add
|
183
|
+
end
|
184
|
+
|
185
|
+
@d.align = CenterAlign
|
186
|
+
insert_text(x, y, percent, {'fill'=> 'black', 'font_weight'=> 700})
|
187
|
+
end
|
188
|
+
|
189
|
+
def set_feature(feature, attribute)
|
190
|
+
case feature
|
191
|
+
when 'fill'
|
192
|
+
@d.fill = attribute
|
193
|
+
when 'font'
|
194
|
+
@d.font = attribute
|
195
|
+
when 'font_family'
|
196
|
+
@d.font_family = attribute
|
197
|
+
when 'font_stretch'
|
198
|
+
@d.font_stretch = attribute
|
199
|
+
when 'font_style'
|
200
|
+
@d.font_style = attribute
|
201
|
+
when 'font_weight'
|
202
|
+
@d.font_weight = attribute
|
203
|
+
when 'stroke'
|
204
|
+
@d.stroke = attribute
|
205
|
+
when 'pointsize'
|
206
|
+
@d.pointsize = attribute
|
207
|
+
when 'text_undercolor'
|
208
|
+
@d.undercolor = attribute
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
metadata
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: analytics_charts
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- STEPHEN YU
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-05-06 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.5'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.5'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rmagick
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
description: |-
|
56
|
+
Initially wrote code on top of gruff library. But eventually to do
|
57
|
+
more fined-tuned data rendering and the need for more lightweight code, I branched out to
|
58
|
+
this new library. Source for original code of gruff library.
|
59
|
+
https://github.com/topfunky/gruff/
|
60
|
+
email:
|
61
|
+
- istephenyu@gmail.com
|
62
|
+
executables: []
|
63
|
+
extensions: []
|
64
|
+
extra_rdoc_files: []
|
65
|
+
files:
|
66
|
+
- .gitignore
|
67
|
+
- Gemfile
|
68
|
+
- LICENSE.txt
|
69
|
+
- README.md
|
70
|
+
- Rakefile
|
71
|
+
- analytics_charts.gemspec
|
72
|
+
- appendix.md
|
73
|
+
- lib/analytics_charts/version.rb
|
74
|
+
- lib/custom_pie.rb
|
75
|
+
homepage: ''
|
76
|
+
licenses:
|
77
|
+
- MIT
|
78
|
+
metadata: {}
|
79
|
+
post_install_message:
|
80
|
+
rdoc_options: []
|
81
|
+
require_paths:
|
82
|
+
- lib
|
83
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
84
|
+
requirements:
|
85
|
+
- - '>='
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
version: '0'
|
88
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
89
|
+
requirements:
|
90
|
+
- - '>='
|
91
|
+
- !ruby/object:Gem::Version
|
92
|
+
version: '0'
|
93
|
+
requirements: []
|
94
|
+
rubyforge_project:
|
95
|
+
rubygems_version: 2.1.9
|
96
|
+
signing_key:
|
97
|
+
specification_version: 4
|
98
|
+
summary: Chart for analytics_charts
|
99
|
+
test_files: []
|