├── .github
└── workflows
│ └── release.yml
├── .gitignore
├── LICENSE.txt
├── README.md
├── README
├── Lollipop.png
└── PureChartLogo.png
├── app
└── views
│ ├── _bar.html.erb
│ ├── _line_plot.html.erb
│ ├── _lollipop.html.erb
│ ├── _pie.html.erb
│ └── _styles.html.erb
├── lib
├── purechart.rb
└── purechart
│ ├── chart_helper.rb
│ └── styles
│ ├── default.yml
│ ├── futuristic_dark.yml
│ ├── futuristic_light.yml
│ ├── professional_dark.yml
│ └── professional_light.yml
└── purechart.gemspec
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: release
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 |
8 | jobs:
9 | release:
10 | runs-on: ubuntu-latest
11 | if: contains(github.event.head_commit.message, 'RELEASE')
12 | steps:
13 | - uses: actions/checkout@v3
14 |
15 | # Setup ruby if a release was created
16 | - uses: ruby/setup-ruby@v1
17 | with:
18 | ruby-version: 3.0.0
19 |
20 | # Publish
21 | - name: publish gem
22 | run: |
23 | mkdir -p $HOME/.gem
24 | touch $HOME/.gem/credentials
25 | chmod 0600 $HOME/.gem/credentials
26 | printf -- "---\n:rubygems_api_key: ${GEM_HOST_API_KEY}\n" > $HOME/.gem/credentials
27 | gem build *.gemspec
28 | gem push *.gem
29 | env:
30 | # Make sure to update the secret name
31 | # if yours isn't named RUBYGEMS_AUTH_TOKEN
32 | GEM_HOST_API_KEY: "${{secrets.RUBYGEMS_AUTH_TOKEN}}"
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | *.gem
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 George Berdovskiy
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | # PureChart
6 | Fully customizable HTML/CSS charts for Ruby on Rails. PureChart serves as an alternative to other charting libraries that extensively use JavaScript and HTML `canvas` elements to render charts, resulting in Rails rendering problems and very limited customization options.
7 |
8 | ## Getting Started
9 | Integrating PureChart into your Rails project is incredibly easy. Simply add `gem 'purechart'` to your Gemfile, run `bundle install` and you should be all set! Now, use our helpers to generate charts in your `html.erb` files and create your own style configurations in either `YML`, `TOML`, or `JSON` format in the `app/purechart` directory of your application.
10 |
11 | **Note -** We're currently working on a documentation website ([docs.purechart.org](https://docs.purechart.org)). Check back once in a while for updates!
12 |
13 | ## Examples
14 | ### Lollipop Chart
15 | #### Controller
16 | ```ruby
17 | class ChartsController < ApplicationController
18 | def index
19 | @data = [
20 | {
21 | name: "Burger King",
22 | color: "#ff7f50",
23 | value: 1200,
24 | },
25 | {
26 | name: "McDonalds",
27 | color: "#ff4757",
28 | value: 500,
29 | },
30 | {
31 | name: "Green Burrito",
32 | color: "#2ed573",
33 | value: 780,
34 | }
35 | ]
36 |
37 | @axes = {
38 | horizontal: "Dollars"
39 | }
40 | end
41 | end
42 | ```
43 |
44 | #### Optional Style Configuration (`app/purechart/custom_bar.yml`)
45 | ```yml
46 | ---
47 | labels:
48 | font: Inter Tight
49 | ...
50 | ```
51 |
52 | #### Template
53 | ```erb
54 |
55 | <%= lollipop_chart @data, @axes, "custom_bar" %>
56 |
57 | ```
58 |
59 |
60 |
61 |
62 |
--------------------------------------------------------------------------------
/README/Lollipop.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PureChart/purechart/7eab14955b37b9355c072453628388127caa218e/README/Lollipop.png
--------------------------------------------------------------------------------
/README/PureChartLogo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PureChart/purechart/7eab14955b37b9355c072453628388127caa218e/README/PureChartLogo.png
--------------------------------------------------------------------------------
/app/views/_bar.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | <%- data.each do |object| %>
5 |
6 |
<%= object[:name] %>
7 |
11 |
12 | <% end %>
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 | <%- (1..10).each do |index| %>
39 |
40 |
<%= index * gridlines[:vertical_increment] %>
41 |
42 | <% end %>
43 |
44 |
45 |
<%= configuration[:axes][:horizontal] %><%= configuration[:symbol] ? " (" + configuration[:symbol] + ")" : ''%>
46 |
47 |
48 |
49 | <%= render :partial => '/styles' %>
--------------------------------------------------------------------------------
/app/views/_line_plot.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
<%= configuration[:axes][:vertical] %><%= configuration[:symbol] ? " (" + configuration[:symbol] + ")" : ''%>
9 |
10 |
11 |
12 | <% data.each_with_index do |object, index| %>
13 | <% left_offset = (object[:name].size)+5; %>
14 | <% vertical_position = 100 %>
15 |
21 | <% (object[:length].to_i).times do |circle_index| %>
22 |
23 |
24 |
25 |
26 |
27 |
28 | <% vertical_position -= 10 %>
29 | <% end %>
30 |
31 |
32 |
33 |
<%= object[:name] %>
34 | <% end %>
35 |
36 |
37 |
38 |
39 |
40 | <%= render :partial => '/styles' %>
41 |
--------------------------------------------------------------------------------
/app/views/_lollipop.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | <%- data.each do |object| %>
5 |
6 |
<%= object[:name] %>
7 |
13 |
14 | <% end %>
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 | <%- (1..10).each do |index| %>
41 |
42 |
<%= index * gridlines[:vertical_increment] %>
43 |
44 | <% end %>
45 |
46 |
47 |
<%= configuration[:axes][:horizontal] %><%= configuration[:symbol] ? " (" + configuration[:symbol] + ")" : ''%>
48 |
49 |
50 |
51 | <%= render :partial => '/styles' %>
--------------------------------------------------------------------------------
/app/views/_pie.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | <%- arc_x = 25 %>
5 | <%- arc_y = 150 %>
6 | <%- cumulative_percent = 0 %>
7 | <%- cumulative_angle = -180 %>
8 | <%# Calculate arc coordinates to draw each slice %>
9 | <%- data.each_with_index do |object, index| %>
10 | <%- cumulative_percent += object[:percent_value] %>
11 |
19 | <%- arc_x = 150 - 125 * Math.cos(2 * Math::PI * cumulative_percent) %>
20 | <%- arc_y = 150 - 125 * Math.sin(2 * Math::PI * cumulative_percent) %>
21 |
22 | <%# Render arc highlights for hover effect %>
23 |
29 | <%- cumulative_angle += 360 * object[:percent_value] %>
30 | <% end %>
31 |
32 | <%# Pie chart key %>
33 |
34 | <%- data.each_with_index do |object, index| %>
35 |
36 |
37 | <%= object[:name] %>
38 |
39 | <% end %>
40 |
41 |
42 |
43 |
44 |
85 |
86 | <%= render :partial => '/styles' %>
--------------------------------------------------------------------------------
/app/views/_styles.html.erb:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/lib/purechart.rb:
--------------------------------------------------------------------------------
1 | require_relative "purechart/chart_helper"
2 |
3 | if defined?(ActiveSupport.on_load)
4 | ActiveSupport.on_load(:action_view) do
5 | include PureChart::ChartHelpers
6 | puts "Helpers sent."
7 | end
8 | else
9 | puts "Active support not defined"
10 | end
11 |
12 | module PureChart
13 | class << self
14 | end
15 |
16 | class Engine < Rails::Engine;
17 | end
18 | end
19 |
--------------------------------------------------------------------------------
/lib/purechart/chart_helper.rb:
--------------------------------------------------------------------------------
1 | module PureChart
2 | module ChartHelpers
3 | def lollipop_chart(data, configuration = { axes: { horizontal: "Value" } }, path="")
4 | # Set default configuration file path
5 | default_config_path = File.join( File.dirname(__FILE__), 'styles/default.yml' )
6 |
7 | default_config_hash = YAML.load(File.read(default_config_path))
8 | user_config_hash = {}
9 |
10 | if path == "professional_light"
11 | # TODO - Instead of loading our own by default, try/catch to see if they defined their own
12 | # style using the same name
13 | style_config_path = File.join( File.dirname(__FILE__), 'styles/professional_light.yml' )
14 | default_config_hash = YAML.load(File.read(style_config_path))
15 | elsif path == "professional_dark"
16 | style_config_path = File.join( File.dirname(__FILE__), 'styles/professional_dark.yml' )
17 | default_config_hash = YAML.load(File.read(style_config_path))
18 | elsif path == "futuristic_light"
19 | style_config_path = File.join( File.dirname(__FILE__), 'styles/futuristic_light.yml' )
20 | default_config_hash = YAML.load(File.read(style_config_path))
21 | elsif path == "futuristic_dark"
22 | style_config_path = File.join( File.dirname(__FILE__), 'styles/futuristic_dark.yml' )
23 | default_config_hash = YAML.load(File.read(style_config_path))
24 | elsif path != ""
25 | # TODO - Implement better logic
26 | if File.file?("app/purechart/" + path + ".yml")
27 | user_config_hash = YAML.load(File.read("app/purechart/" + path + ".yml"))
28 | elsif File.file?("app/purechart/" + path + ".yaml")
29 | user_config_hash = YAML.load(File.read("app/purechart/" + path + ".yaml"))
30 | elsif File.file?("app/purechart/" + path + ".json")
31 | user_config_hash = JSON.load(File.read("app/purechart/" + path + ".json"))
32 | else
33 | raise "(PureChart) ERROR - Could not locate configuration file '" + path + ".[YML, YAML, JSON]'. Make sure this file exists in your 'app/purechart' directory."
34 | end
35 | end
36 |
37 | # Merge user's configuration with default
38 | style_config = default_config_hash.merge(user_config_hash)
39 |
40 | # Format data for chart generation
41 | largest_value = (data.map { |object| object[:value] }).max()
42 |
43 | data.each do |object|
44 | object[:width] = (Float(object[:value]) / largest_value) * 100
45 | end
46 |
47 | gridlines = {
48 | vertical_lines: 10,
49 | vertical_increment: (Float(largest_value) / 10).ceil
50 | }
51 |
52 | ActionController::Base.render partial: '/lollipop', locals: {
53 | data: data,
54 | gridlines: gridlines,
55 | configuration: configuration,
56 | style: style_config
57 | }
58 | end
59 |
60 | def bar_chart(data, configuration = { axes: { horizontal: "Value" }, corner_radius: "Value" }, path="")
61 | # Set default configuration file path
62 | default_config_path = File.join( File.dirname(__FILE__), 'styles/default.yml' )
63 |
64 | default_config_hash = YAML.load(File.read(default_config_path))
65 | user_config_hash = {}
66 |
67 | if path == "professional_light"
68 | # TODO - Instead of loading our own by default, try/catch to see if they defined their own
69 | # style using the same name
70 | style_config_path = File.join( File.dirname(__FILE__), 'styles/professional_light.yml' )
71 | default_config_hash = YAML.load(File.read(style_config_path))
72 | elsif path == "professional_dark"
73 | style_config_path = File.join( File.dirname(__FILE__), 'styles/professional_dark.yml' )
74 | default_config_hash = YAML.load(File.read(style_config_path))
75 | elsif path != ""
76 | # TODO - Implement better logic
77 | if File.file?("app/purechart/" + path + ".yml")
78 | user_config_hash = YAML.load(File.read("app/purechart/" + path + ".yml"))
79 | elsif File.file?("app/purechart/" + path + ".yaml")
80 | user_config_hash = YAML.load(File.read("app/purechart/" + path + ".yaml"))
81 | elsif File.file?("app/purechart/" + path + ".json")
82 | user_config_hash = JSON.load(File.read("app/purechart/" + path + ".json"))
83 | else
84 | raise "(PureChart) ERROR - Could not locate configuration file '" + path + ".[YML, YAML, JSON]'. Make sure this file exists in your 'app/purechart' directory."
85 | end
86 | end
87 |
88 | # Merge user's configuration with default
89 | style_config = default_config_hash.merge(user_config_hash)
90 |
91 | # Format data for chart generation
92 | largest_value = (data.map { |object| object[:value] }).max()
93 |
94 | data.each do |object|
95 | object[:width] = (Float(object[:value]) / largest_value) * 100
96 | end
97 |
98 | gridlines = {
99 | vertical_lines: 10,
100 | vertical_increment: (Float(largest_value) / 10).ceil
101 | }
102 |
103 | ActionController::Base.render partial: '/bar', locals: {
104 | data: data,
105 | gridlines: gridlines,
106 | configuration: configuration,
107 | style: style_config
108 | }
109 | end
110 |
111 | def column_chart
112 | "Column chart will be rendered here.
".html_safe
113 | end
114 |
115 | def pie_chart(data)
116 | # check for negative values
117 | has_negative_value = 0
118 |
119 | # Find total value for calculating percentages
120 | total_value = 0
121 | data.each do |object|
122 | total_value += (object[:value]).abs
123 | end
124 |
125 | # Calculate percentages for each data point
126 | data.each do |object|
127 | object[:percent_value] = (Float(object[:value]) / total_value).abs
128 | if object[:value] < 0
129 | object[:is_negative] = 1
130 | has_negative_value = 1
131 | else
132 | object[:is_negative] = 0
133 | end
134 | end
135 |
136 | negative_value = {
137 | negative: has_negative_value
138 | }
139 |
140 | ActionController::Base.render partial: '/pie', locals: {
141 | data: data,
142 | negative_value: negative_value
143 | }
144 | end
145 |
146 | def box_plot(data, configuration = {}, path="")
147 | default_color_config = {
148 | colors: {
149 | fill: "white",
150 | stroke: "black"
151 | },
152 | width: 1
153 | }
154 |
155 | merged_color_config = default_color_config.merge(configuration)
156 |
157 | fill_color = merged_color_config.dig(:colors, :fill)
158 | stroke_color = merged_color_config.dig(:colors, :stroke)
159 | stroke_width = merged_color_config[:width]
160 |
161 | default_config_path = File.join(File.dirname(__FILE__), 'styles', 'default.yml')
162 | default_config_hash = YAML.load(File.read(default_config_path))
163 | user_config_hash = {}
164 |
165 | if path == "professional_light"
166 | # TODO - Instead of loading our own by default, try/catch to see if they defined their own
167 | # style using the same name
168 | style_config_path = File.join( File.dirname(__FILE__), 'styles/professional_light.yml' )
169 | default_config_hash = YAML.load(File.read(style_config_path))
170 | elsif path == "professional_dark"
171 | style_config_path = File.join( File.dirname(__FILE__), 'styles/professional_dark.yml' )
172 | default_config_hash = YAML.load(File.read(style_config_path))
173 | elsif path == "futuristic_light"
174 | style_config_path = File.join( File.dirname(__FILE__), 'styles/futuristic_light.yml' )
175 | default_config_hash = YAML.load(File.read(style_config_path))
176 | elsif path == "futuristic_dark"
177 | style_config_path = File.join( File.dirname(__FILE__), 'styles/futuristic_dark.yml' )
178 | default_config_hash = YAML.load(File.read(style_config_path))
179 | elsif path == "default"
180 | style_config_path = File.join( File.dirname(__FILE__), 'styles/default.yml' )
181 | default_config_hash = YAML.load(File.read(style_config_path))
182 | elsif path != ""
183 | # TODO - Implement better logic
184 | if File.file?("app/purechart/" + path + ".yml")
185 | user_config_hash = YAML.load(File.read("app/purechart/" + path + ".yml"))
186 | elsif File.file?("app/purechart/" + path + ".yaml")
187 | user_config_hash = YAML.load(File.read("app/purechart/" + path + ".yaml"))
188 | elsif File.file?("app/purechart/" + path + ".json")
189 | user_config_hash = JSON.load(File.read("app/purechart/" + path + ".json"))
190 | else
191 | raise "(PureChart) ERROR - Could not locate configuration file '" + path + ".[YML, YAML, JSON]'. Make sure this file exists in your 'app/purechart' directory."
192 | end
193 | end
194 |
195 | # Merge user's configuration with default
196 | style_config = default_config_hash.merge(user_config_hash)
197 | area = ''''''
198 |
199 | s_data = data.map{ |data| data[:value]}.sort
200 |
201 | def find_median(array)
202 | return nil if array.empty?
203 | if array.length.even?
204 | puts array.length
205 | return (array[array.length/2]+array[array.length/2-1])/2.0
206 | else
207 | return array[array.length/2].to_f;
208 | end
209 | end
210 |
211 | def normalize(value,min,max)
212 | return ((value.to_f-(min))/(max.to_f-min)+0.025)*950.0
213 | end
214 |
215 | n = s_data.length
216 | min = s_data[0]
217 | max = s_data[-1]
218 | median = find_median(s_data)
219 |
220 | if s_data.length.even?
221 | lower = find_median s_data[0..n/2-1]
222 | upper = find_median s_data[n/2..-1]
223 | else
224 | lower = find_median s_data[0..n/2-1]
225 | upper = find_median s_data[n/2+1..-1]
226 | end
227 |
228 | iqr = 1.5*(upper - lower)
229 |
230 | # color selection from YML - FIX LATER
231 | # Q1 and Q3
232 | area += " "
234 | area += " "
236 |
237 | def linemaker(start,endp, stroke, stroke_width)
238 | line = ""
239 | line+=" "
240 | line+=" "
241 | return line
242 | end
243 |
244 | # min wisker + lower outliers
245 | if min >= lower-iqr
246 | area+=linemaker(normalize(lower,min,max),normalize(min,min,max), stroke_color, stroke_width)
247 | else
248 | area+=linemaker(normalize(lower,min,max),(normalize(lower-iqr,min,max)), stroke_color, stroke_width)
249 | i=0
250 | while s_data[i] <= lower-iqr
251 | area+= " "
252 | i+=1
253 | end
254 | end
255 |
256 | # max wisker + upper outliers
257 | if max <= upper+iqr
258 | area+=linemaker(normalize(upper,min,max),normalize(max,min,max), stroke_color, stroke_width)
259 | else
260 | area+=linemaker(normalize(upper,min,max),(normalize(upper+iqr,min,max)), stroke_color, stroke_width)
261 | i=s_data.length-1
262 | while s_data[i] >= upper+iqr
263 | area+= " "
264 | i-=1
265 | end
266 | end
267 |
268 | area+=" "
269 | area.html_safe
270 | end
271 |
272 | def line_graph(data)
273 | # If all values are very high, the "adjust factor" will be used
274 | # to make sure they are all evenly spread across the vertical axis
275 | # TODO - Decide what the "adjust factor" should be based on user
276 | # input... also rename it
277 | adjust_factor = 0
278 |
279 | chart = ''''''
280 |
281 | min_val = data.min - adjust_factor
282 | max_val = data.max + adjust_factor
283 |
284 | # Calculate the vertical scaling factor
285 | scale_factor = 500.to_f / (max_val - min_val)
286 |
287 | prev_x = 10
288 | prev_y = -1
289 |
290 | i = 10
291 | data.each do |val|
292 | # Apply the transformation formula to scale the y coordinate
293 | scaled_y = ((val - min_val) * scale_factor)
294 | # Invert the y-axis to match the SVG coordinate system (0 at the top)
295 | inverted_y = 500 - scaled_y
296 |
297 | if prev_y == -1
298 | prev_y = inverted_y
299 | end
300 |
301 | chart += " "
302 | chart += " "
303 |
304 | prev_x = i
305 | prev_y = inverted_y
306 |
307 | i += 480.to_f / data.length
308 | end
309 |
310 | chart += " "
311 | chart.html_safe
312 | end
313 |
314 | def dot_plot(data)
315 | adjust_factor = 0
316 | chart = ''''''
317 |
318 | min_val = [0, data.min - adjust_factor].min
319 | max_val = data.max + adjust_factor
320 |
321 | # Calculate the vertical scaling factor
322 | scale_factor = 480.to_f / (max_val - min_val)
323 | x_scale_factor = 480.to_f / data.length
324 |
325 | # Calculate the number of ticks and the increment based on the data range
326 | data_range = max_val - min_val
327 | num_ticks = [data.length, 10].min
328 | increment = data_range.to_f / num_ticks
329 |
330 | # Draw ticks and grey lines for the y-axis
331 | (0..num_ticks).each do |index|
332 | value = min_val + (index * increment)
333 | y = 500 - (index * (450 / num_ticks))
334 |
335 | if (value % 1).zero?
336 | formatted_value = value.to_i
337 | else
338 | formatted_value = "%.1f" % value
339 | end
340 |
341 | # Only add label and line if value is not 0
342 | if value != 0
343 | chart += " "
344 | chart += "#{formatted_value} "
345 | end
346 | end
347 |
348 | # dots
349 | data.each_with_index do |val, index|
350 | scaled_y = ((val - min_val) * scale_factor)
351 | inverted_y = 500 - (scaled_y / 48 * 45)
352 | chart += " "
353 | end
354 |
355 | # x and y axis
356 | chart += " "
357 | chart += " "
358 | chart += " "
359 | chart.html_safe
360 | end
361 |
362 | def line_plot(data, configuration = { axes: { vertical: "Value" } }, path="")
363 | # Set default configuration file path
364 | default_config_path = File.join( File.dirname(__FILE__), 'styles/default.yml' )
365 |
366 | default_config_hash = YAML.load(File.read(default_config_path))
367 | user_config_hash = {}
368 |
369 | if path == "professional_light"
370 | # TODO - Instead of loading our own by default, try/catch to see if they defined their own
371 | # style using the same name
372 | style_config_path = File.join( File.dirname(__FILE__), 'styles/professional_light.yml' )
373 | default_config_hash = YAML.load(File.read(style_config_path))
374 | elsif path == "professional_dark"
375 | style_config_path = File.join( File.dirname(__FILE__), 'styles/professional_dark.yml' )
376 | default_config_hash = YAML.load(File.read(style_config_path))
377 | elsif path == "futuristic_light"
378 | style_config_path = File.join( File.dirname(__FILE__), 'styles/futuristic_light.yml' )
379 | default_config_hash = YAML.load(File.read(style_config_path))
380 | elsif path == "futuristic_dark"
381 | style_config_path = File.join( File.dirname(__FILE__), 'styles/futuristic_dark.yml' )
382 | default_config_hash = YAML.load(File.read(style_config_path))
383 | elsif path != ""
384 | # TODO - Implement better logic
385 | if File.file?("app/purechart/" + path + ".yml")
386 | user_config_hash = YAML.load(File.read("app/purechart/" + path + ".yml"))
387 | elsif File.file?("app/purechart/" + path + ".yaml")
388 | user_config_hash = YAML.load(File.read("app/purechart/" + path + ".yaml"))
389 | elsif File.file?("app/purechart/" + path + ".json")
390 | user_config_hash = JSON.load(File.read("app/purechart/" + path + ".json"))
391 | else
392 | raise "(PureChart) ERROR - Could not locate configuration file '" + path + ".[YML, YAML, JSON]'. Make sure this file exists in your 'app/purechart' directory."
393 | end
394 | end
395 |
396 | # Merge user's configuration with default
397 | style_config = default_config_hash.merge(user_config_hash)
398 |
399 | # Format data for chart generation
400 | largest_value = (data.map { |object| object[:value] }).max()
401 |
402 | data.each do |object|
403 | object[:length] = (Float(object[:value]) / largest_value) * 10
404 | end
405 |
406 | gridlines = {
407 | vertical_lines: data.size
408 | }
409 |
410 | ActionController::Base.render partial: '/line_plot', locals: {
411 | data: data,
412 | gridlines: gridlines,
413 | configuration: configuration,
414 | style: style_config
415 | }
416 | end
417 | end
418 | end
419 |
--------------------------------------------------------------------------------
/lib/purechart/styles/default.yml:
--------------------------------------------------------------------------------
1 | ---
2 | title:
3 | font: Inter Tight
4 | weight: 700
5 | color: "#000000"
6 | labels:
7 | font: Inter Tight
8 | weight: 700
9 | color: "#000000"
10 | ticks:
11 | font: Inter Tight
12 | weight: 400
13 | color: "#000000"
14 | axes:
15 | style: "2px solid #000000"
16 | gridlines:
17 | style: "2px dashed #00000033"
18 | colors:
19 | red: '#eb3b5a'
20 | orange: '#fa8231'
21 | yellow: '#f7b731'
22 | green: '#20bf6b'
23 | blue: '#4b7bec'
24 | purple: '#a55eea'
25 | ...
--------------------------------------------------------------------------------
/lib/purechart/styles/futuristic_dark.yml:
--------------------------------------------------------------------------------
1 | ---
2 | title:
3 | font: Futura
4 | weight: 700
5 | color: "#FFFFFF"
6 | labels:
7 | font: Futura
8 | weight: 700
9 | color: "#FFFFFF"
10 | ticks:
11 | font: Futura
12 | weight: 400
13 | color: "#FFFFFF"
14 | axes:
15 | style: "2px solid #FFFFFF"
16 | gridlines:
17 | style: "2px dashed #FFFFFF33"
18 | colors:
19 | green: '#90EE90'
20 | red: '#FF6347'
21 | orange: '#FFA500'
22 | blue: '#1E90FF'
23 | yellow: '#FFFF00'
24 | purple: '#BA55D3'
25 | brown: '#A52A2A'
26 | grey: '#808080'
27 | ...
28 |
29 |
--------------------------------------------------------------------------------
/lib/purechart/styles/futuristic_light.yml:
--------------------------------------------------------------------------------
1 | ---
2 | title:
3 | font: Futura
4 | weight: 700
5 | color: "#000000"
6 | labels:
7 | font: Futura
8 | weight: 700
9 | color: "#000000"
10 | ticks:
11 | font: Futura
12 | weight: 400
13 | color: "#000000"
14 | axes:
15 | style: "2px solid #000000"
16 | gridlines:
17 | style: "2px dashed #00000033"
18 | colors:
19 | green: '#478279'
20 | red: '#e2458c'
21 | orange: '#f7931b'
22 | blue: '#00c2ff'
23 | yellow: '#ffc107'
24 | purple: '#943ca3'
25 | brown: '#8a5b3f'
26 | grey: '#808080'
27 | ...
--------------------------------------------------------------------------------
/lib/purechart/styles/professional_dark.yml:
--------------------------------------------------------------------------------
1 | title:
2 | font: 'Times New Roman'
3 | weight: 800
4 | color: '#CCCCCC'
5 |
6 | labels:
7 | font: 'Times New Roman'
8 | weight: 800
9 | color: '#AAAAAA'
10 |
11 | ticks:
12 | font: 'Times New Roman'
13 | weight: 900
14 | color: '#777777'
15 |
16 | axes:
17 | style: '2px solid #FFFFFF'
18 |
19 | gridlines:
20 | style: '2px dashed #FFFFFF33'
21 |
22 | colors:
23 | muted-blue: '#445577'
24 | muted-green: '#4C5E4F'
25 | muted-pink: '#8D6F6A'
26 | muted-purple: '#725A72'
27 | muted-teal: '#465C5C'
28 | muted-peach: '#DAAF93'
29 | muted-gray: '#909292'
30 | muted-olive: '#7D7F6A'
31 | muted-brown: '#7A6E63'
32 | muted-slate: '#57646C'
33 | muted-lavender: '#8F7FA2'
34 | muted-coral: '#E06B58'
35 | muted-mint: '#539E87'
36 | muted-lilac: '#A687AD'
37 | muted-sand: '#D9C997'
38 |
--------------------------------------------------------------------------------
/lib/purechart/styles/professional_light.yml:
--------------------------------------------------------------------------------
1 | title:
2 | font: 'Times New Roman'
3 | weight: 800
4 | color: '#333333'
5 |
6 | labels:
7 | font: 'Times New Roman'
8 | weight: 800
9 | color: '#666666'
10 |
11 | ticks:
12 | font: 'Times New Roman'
13 | weight: 900
14 | color: '#999999'
15 |
16 | axes:
17 | style: '2px solid #333333'
18 |
19 | gridlines:
20 | style: '2px dashed #33333333'
21 |
22 | colors:
23 | muted-blue: '#A7C6E4'
24 | muted-green: '#B5CEBF'
25 | muted-pink: '#F2C9C4'
26 | muted-purple: '#D5C1D2'
27 | muted-teal: '#A4BEC0'
28 | muted-peach: '#F8DCC8'
29 | muted-gray: '#D6D7D7'
30 | muted-olive: '#C2C4AB'
31 | muted-brown: '#C0B8AF'
32 | muted-slate: '#A6B0B9'
33 | muted-lavender: '#D5C9DA'
34 | muted-coral: '#FAC5BA'
35 | muted-mint: '#BEEAD2'
36 | muted-lilac: '#E4D4EB'
37 | muted-sand: '#F6EDD5'
38 |
--------------------------------------------------------------------------------
/purechart.gemspec:
--------------------------------------------------------------------------------
1 | Gem::Specification.new do |s|
2 | s.name = "purechart"
3 | s.version = "0.0.5"
4 | s.summary = "PureChart"
5 | s.description = "Pure HTML/CSS charts for Ruby on Rails."
6 | s.authors = ["George Berdovskiy"]
7 | s.files = Dir['lib/**/*'] + Dir['app/**/*']
8 | s.license = "MIT"
9 | end
10 |
--------------------------------------------------------------------------------