├── .github
└── FUNDING.yml
├── .gitignore
├── Gemfile
├── Gemfile.lock
├── MIT-LICENSE
├── README.md
├── Rakefile
├── app
└── assets
│ └── javascripts
│ ├── peity-vanilla-rails.esm.js
│ ├── peity-vanilla-rails.min.js
│ ├── peity-vanilla.esm.js
│ └── peity-vanilla.min.js
├── bin
└── test
├── config
└── importmap.rb
├── docs
└── sparklines.png
├── javascript
├── index.js
├── package.json
├── peity_vanilla.js
└── rollup.config.js
├── lib
├── peity_vanilla_rails.rb
├── peity_vanilla_rails
│ ├── engine.rb
│ ├── helpers.rb
│ └── version.rb
└── tasks
│ └── peity_vanilla_rails_tasks.rake
├── peity_vanilla_rails.gemspec
└── test
├── dummy
├── Rakefile
├── app
│ ├── assets
│ │ ├── config
│ │ │ └── manifest.js
│ │ ├── images
│ │ │ └── .keep
│ │ ├── javascripts
│ │ │ └── application.js
│ │ └── stylesheets
│ │ │ └── application.css
│ ├── channels
│ │ └── application_cable
│ │ │ ├── channel.rb
│ │ │ └── connection.rb
│ ├── controllers
│ │ ├── application_controller.rb
│ │ ├── concerns
│ │ │ └── .keep
│ │ └── home_controller.rb
│ ├── helpers
│ │ ├── application_helper.rb
│ │ └── home_helper.rb
│ ├── jobs
│ │ └── application_job.rb
│ ├── mailers
│ │ └── application_mailer.rb
│ ├── models
│ │ ├── application_record.rb
│ │ └── concerns
│ │ │ └── .keep
│ └── views
│ │ ├── home
│ │ └── index.html.erb
│ │ └── layouts
│ │ ├── application.html.erb
│ │ ├── mailer.html.erb
│ │ └── mailer.text.erb
├── bin
│ ├── rails
│ ├── rake
│ └── setup
├── config.ru
├── config
│ ├── application.rb
│ ├── boot.rb
│ ├── cable.yml
│ ├── database.yml
│ ├── environment.rb
│ ├── environments
│ │ ├── development.rb
│ │ ├── production.rb
│ │ └── test.rb
│ ├── initializers
│ │ ├── content_security_policy.rb
│ │ ├── filter_parameter_logging.rb
│ │ ├── inflections.rb
│ │ └── permissions_policy.rb
│ ├── locales
│ │ └── en.yml
│ ├── puma.rb
│ ├── routes.rb
│ └── storage.yml
├── lib
│ └── assets
│ │ └── .keep
├── log
│ └── .keep
├── node_modules
│ └── .yarn-integrity
├── public
│ ├── 404.html
│ ├── 422.html
│ ├── 500.html
│ ├── apple-touch-icon-precomposed.png
│ ├── apple-touch-icon.png
│ └── favicon.ico
├── test
│ └── controllers
│ │ └── home_controller_test.rb
└── yarn.lock
├── peity_vanilla_rails_test.rb
└── test_helper.rb
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | patreon: igorkasyanchuk
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /.bundle/
2 | /doc/
3 | /log/*.log
4 | /pkg/
5 | /tmp/
6 | /test/dummy/db/*.sqlite3
7 | /test/dummy/db/*.sqlite3-*
8 | /test/dummy/log/*.log
9 | /test/dummy/storage/
10 | /test/dummy/tmp/
11 |
12 | javascript/yarn.lock
13 | javascript/node_modules
14 |
15 | *.gem
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source "https://rubygems.org"
2 | git_source(:github) { |repo| "https://github.com/#{repo}.git" }
3 |
4 | # Specify your gem's dependencies in peity_vanilla_rails.gemspec.
5 | gemspec
6 |
7 | gem "sqlite3"
8 |
9 | # Start debugger with binding.b [https://github.com/ruby/debug]
10 | # gem "debug", ">= 1.0.0"
11 |
12 | gem 'puma'
13 | gem "sprockets-rails"
--------------------------------------------------------------------------------
/Gemfile.lock:
--------------------------------------------------------------------------------
1 | PATH
2 | remote: .
3 | specs:
4 | peity_vanilla_rails (0.2.2)
5 | rails
6 |
7 | GEM
8 | remote: https://rubygems.org/
9 | specs:
10 | actioncable (7.0.2.4)
11 | actionpack (= 7.0.2.4)
12 | activesupport (= 7.0.2.4)
13 | nio4r (~> 2.0)
14 | websocket-driver (>= 0.6.1)
15 | actionmailbox (7.0.2.4)
16 | actionpack (= 7.0.2.4)
17 | activejob (= 7.0.2.4)
18 | activerecord (= 7.0.2.4)
19 | activestorage (= 7.0.2.4)
20 | activesupport (= 7.0.2.4)
21 | mail (>= 2.7.1)
22 | net-imap
23 | net-pop
24 | net-smtp
25 | actionmailer (7.0.2.4)
26 | actionpack (= 7.0.2.4)
27 | actionview (= 7.0.2.4)
28 | activejob (= 7.0.2.4)
29 | activesupport (= 7.0.2.4)
30 | mail (~> 2.5, >= 2.5.4)
31 | net-imap
32 | net-pop
33 | net-smtp
34 | rails-dom-testing (~> 2.0)
35 | actionpack (7.0.2.4)
36 | actionview (= 7.0.2.4)
37 | activesupport (= 7.0.2.4)
38 | rack (~> 2.0, >= 2.2.0)
39 | rack-test (>= 0.6.3)
40 | rails-dom-testing (~> 2.0)
41 | rails-html-sanitizer (~> 1.0, >= 1.2.0)
42 | actiontext (7.0.2.4)
43 | actionpack (= 7.0.2.4)
44 | activerecord (= 7.0.2.4)
45 | activestorage (= 7.0.2.4)
46 | activesupport (= 7.0.2.4)
47 | globalid (>= 0.6.0)
48 | nokogiri (>= 1.8.5)
49 | actionview (7.0.2.4)
50 | activesupport (= 7.0.2.4)
51 | builder (~> 3.1)
52 | erubi (~> 1.4)
53 | rails-dom-testing (~> 2.0)
54 | rails-html-sanitizer (~> 1.1, >= 1.2.0)
55 | activejob (7.0.2.4)
56 | activesupport (= 7.0.2.4)
57 | globalid (>= 0.3.6)
58 | activemodel (7.0.2.4)
59 | activesupport (= 7.0.2.4)
60 | activerecord (7.0.2.4)
61 | activemodel (= 7.0.2.4)
62 | activesupport (= 7.0.2.4)
63 | activestorage (7.0.2.4)
64 | actionpack (= 7.0.2.4)
65 | activejob (= 7.0.2.4)
66 | activerecord (= 7.0.2.4)
67 | activesupport (= 7.0.2.4)
68 | marcel (~> 1.0)
69 | mini_mime (>= 1.1.0)
70 | activesupport (7.0.2.4)
71 | concurrent-ruby (~> 1.0, >= 1.0.2)
72 | i18n (>= 1.6, < 2)
73 | minitest (>= 5.1)
74 | tzinfo (~> 2.0)
75 | builder (3.2.4)
76 | coderay (1.1.3)
77 | concurrent-ruby (1.1.10)
78 | crass (1.0.6)
79 | digest (3.1.0)
80 | erubi (1.10.0)
81 | globalid (1.0.0)
82 | activesupport (>= 5.0)
83 | i18n (1.10.0)
84 | concurrent-ruby (~> 1.0)
85 | loofah (2.17.0)
86 | crass (~> 1.0.2)
87 | nokogiri (>= 1.5.9)
88 | mail (2.7.1)
89 | mini_mime (>= 0.1.1)
90 | marcel (1.0.2)
91 | method_source (1.0.0)
92 | mini_mime (1.1.2)
93 | minitest (5.15.0)
94 | net-imap (0.2.3)
95 | digest
96 | net-protocol
97 | strscan
98 | net-pop (0.1.1)
99 | digest
100 | net-protocol
101 | timeout
102 | net-protocol (0.1.3)
103 | timeout
104 | net-smtp (0.3.1)
105 | digest
106 | net-protocol
107 | timeout
108 | nio4r (2.5.8)
109 | nokogiri (1.13.4-arm64-darwin)
110 | racc (~> 1.4)
111 | nokogiri (1.13.4-x86_64-darwin)
112 | racc (~> 1.4)
113 | pry (0.14.1)
114 | coderay (~> 1.1)
115 | method_source (~> 1.0)
116 | puma (5.6.4)
117 | nio4r (~> 2.0)
118 | racc (1.6.0)
119 | rack (2.2.3)
120 | rack-test (1.1.0)
121 | rack (>= 1.0, < 3)
122 | rails (7.0.2.4)
123 | actioncable (= 7.0.2.4)
124 | actionmailbox (= 7.0.2.4)
125 | actionmailer (= 7.0.2.4)
126 | actionpack (= 7.0.2.4)
127 | actiontext (= 7.0.2.4)
128 | actionview (= 7.0.2.4)
129 | activejob (= 7.0.2.4)
130 | activemodel (= 7.0.2.4)
131 | activerecord (= 7.0.2.4)
132 | activestorage (= 7.0.2.4)
133 | activesupport (= 7.0.2.4)
134 | bundler (>= 1.15.0)
135 | railties (= 7.0.2.4)
136 | rails-dom-testing (2.0.3)
137 | activesupport (>= 4.2.0)
138 | nokogiri (>= 1.6)
139 | rails-html-sanitizer (1.4.2)
140 | loofah (~> 2.3)
141 | railties (7.0.2.4)
142 | actionpack (= 7.0.2.4)
143 | activesupport (= 7.0.2.4)
144 | method_source
145 | rake (>= 12.2)
146 | thor (~> 1.0)
147 | zeitwerk (~> 2.5)
148 | rake (13.0.6)
149 | sprockets (4.0.3)
150 | concurrent-ruby (~> 1.0)
151 | rack (> 1, < 3)
152 | sprockets-rails (3.4.2)
153 | actionpack (>= 5.2)
154 | activesupport (>= 5.2)
155 | sprockets (>= 3.0.0)
156 | sqlite3 (1.4.2)
157 | strscan (3.0.3)
158 | thor (1.2.1)
159 | timeout (0.2.0)
160 | tzinfo (2.0.4)
161 | concurrent-ruby (~> 1.0)
162 | websocket-driver (0.7.5)
163 | websocket-extensions (>= 0.1.0)
164 | websocket-extensions (0.1.5)
165 | zeitwerk (2.5.4)
166 |
167 | PLATFORMS
168 | arm64-darwin-21
169 | x86_64-darwin-21
170 |
171 | DEPENDENCIES
172 | peity_vanilla_rails!
173 | pry
174 | puma
175 | sprockets-rails
176 | sqlite3
177 |
178 | BUNDLED WITH
179 | 2.3.7
180 |
--------------------------------------------------------------------------------
/MIT-LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2022
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining
4 | a copy of this software and associated documentation files (the
5 | "Software"), to deal in the Software without restriction, including
6 | without limitation the rights to use, copy, modify, merge, publish,
7 | distribute, sublicense, and/or sell copies of the Software, and to
8 | permit persons to whom the Software is furnished to do so, subject to
9 | the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be
12 | included in all copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Peity Vanilla Rails
2 |
3 | [](https://www.railsjazz.com)
4 |
5 | [](https://buymeacoffee.com/igorkasyanchuk)
6 |
7 | Sparklines are small but intense charts. This gem is a wrapper around [peity-vanilla](https://github.com/railsjazz/peity_vanilla) library. You can generate simple but informative charts with vanilla JS.
8 |
9 |
10 |
11 | ## Usage
12 |
13 | 1. add gem to the `Gemfile`
14 |
15 | ```ruby
16 | gem "peity_vanilla_rails"
17 | ```
18 |
19 | 2. Add to `application.js`
20 |
21 | ### For Assets Pipeline:
22 |
23 | ```javascript
24 | //= require peity-vanilla-rails.js
25 | ```
26 |
27 | ### For Importmaps
28 |
29 | In `application.js`
30 |
31 | ```js
32 | import "peity-vanilla-rails";
33 | ```
34 |
35 | #### Note: After `peity-vanilla-rails.js` is imported, it will listen to changes of `peity` and `data-peity` attributes of every DOM element
36 | Pure `peity-vanilla` library is also acessible via `peity-vanilla.js` for Assets Pipeline, and `import peity from "peity-vanilla"` for Importmaps
37 |
38 | 3. Add charts in your code:
39 |
40 | ```erb
41 |
Line
42 | <%= peity_line_chart([115,123,234,-113,-43,-223,127,332,152,233]) %>
43 | <%= peity_line_chart(100.times.map{rand(100) * [1,-1].sample}, options: { width: 240, fill: 'lightgreen', stroke: 'blue' }) %>
44 |
45 | Bar
46 | <%= peity_bar_chart([115,123,234,-113,-43,-223,127,332,152,233]) %>
47 | <%= peity_bar_chart('115,123,234,-132,152,233') %>
48 | <%= peity_bar_chart(50.times.map{rand(100) }, options: { width: 240, fill: ['orange'], height: 30, padding: -0.1 }) %>
49 |
50 | Pie
51 | <%= peity_pie_chart "2/3" %>
52 | <%= peity_pie_chart [3,10] %>
53 | <%= peity_pie_chart [3,10], options: { fill: ["red", "#eeeeee"], radius: 10 } %>
54 | <%= peity_pie_chart [236,300] %>
55 |
56 | Donut
57 | <%= peity_donut_chart "2/3" %>
58 | <%= peity_donut_chart [6,20] %>
59 | <%= peity_donut_chart [236,300] %>
60 | ```
61 |
62 | ## More Examples
63 |
64 | Check the [original](https://github.com/railsjazz/peity_vanilla) page.
65 |
66 |
67 |
68 |
69 |
70 | ```erb
71 | <%= peity_line_chart([5,3,9,6,5,9,7,3,5,2,5,3,9,6,5,9,7,3,5,2], id: 'updating-chart') %>
72 |
73 |
85 | ```
86 |
87 | ## Default Options
88 |
89 | More information here: https://github.com/railsjazz/peity_vanilla.
90 |
91 | You can pass in `options` any of the attributes.
92 |
93 | ```html
94 |
133 | ```
134 |
135 | ## TODO
136 |
137 | - remote datasource and autoupdate
138 |
139 | ## Contributing
140 |
141 | You are welcome to contribute.
142 |
143 | ## License
144 |
145 | The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
146 |
147 |
148 | [
](https://www.railsjazz.com/?utm_source=github&utm_medium=bottom&utm_campaign=peity_vanilla_rails)
150 |
151 | [](https://buymeacoffee.com/igorkasyanchuk)
152 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | require "bundler/setup"
2 |
3 | require "bundler/gem_tasks"
4 |
--------------------------------------------------------------------------------
/app/assets/javascripts/peity-vanilla-rails.esm.js:
--------------------------------------------------------------------------------
1 | /*!
2 | Peity Vanila Rails 0.2.2
3 | Copyright © 2022 RailsJazz
4 | https://railsjazz.com
5 | */
6 |
7 | /*!
8 | Peity Vanila JS 0.0.8
9 | Copyright © 2022 RailsJazz
10 | https://railsjazz.com
11 | */
12 |
13 | const isFunction = (o) =>
14 | o !== null && typeof o === "function" && !!o.apply;
15 |
16 | const svgElement = (tag, attrs) => {
17 | const element = document.createElementNS("http://www.w3.org/2000/svg", tag);
18 | for (var attr in attrs) {
19 | element.setAttribute(attr, attrs[attr]);
20 | }
21 | return element;
22 | };
23 |
24 | const svgSupported =
25 | "createElementNS" in document && svgElement("svg", {}).createSVGRect();
26 |
27 | class Peity {
28 | static defaults = {};
29 | static graphers = {};
30 |
31 | constructor(element, type, options = {}) {
32 | this.element = element;
33 | this.type = type;
34 | this.options = Object.assign(
35 | {},
36 | Peity.defaults[this.type],
37 | JSON.parse(element.dataset["peity"] || "{}"),
38 | options
39 | );
40 |
41 | if(this.element._peity) {
42 | this.element._peity.destroy();
43 | }
44 | this.element._peity = this;
45 | }
46 |
47 | draw() {
48 | const options = this.options;
49 | Peity.graphers[this.type](this);
50 | if (isFunction(options.after)) options.after.call(this, options);
51 | }
52 |
53 | fill() {
54 | var fill = this.options.fill;
55 |
56 | return isFunction(fill)
57 | ? fill
58 | : function (_, i) {
59 | return fill[i % fill.length];
60 | };
61 | }
62 |
63 | prepare(width, height) {
64 | if (!this.svg) {
65 | this.element.style.display = "none";
66 | this.element.after(
67 | (this.svg = svgElement("svg", {
68 | class: "peity",
69 | }))
70 | );
71 | }
72 |
73 | this.svg.innerHTML = "";
74 | this.svg.setAttribute("width", width);
75 | this.svg.setAttribute("height", height);
76 |
77 | return this.svg;
78 | }
79 |
80 | get values() {
81 | return this.element.innerText
82 | .split(this.options.delimiter)
83 | .map((value) => parseFloat(value));
84 | }
85 |
86 | mount() {
87 | if (!svgSupported) return;
88 |
89 | this.element.addEventListener("DOMSubtreeModified", this.draw.bind(this));
90 | this.draw();
91 |
92 | this.mounted = true;
93 | }
94 |
95 | unmount() {
96 | this.element.removeEventListener("DOMSubtreeModified", this.draw);
97 | this.svg.remove();
98 | this.mounted = false;
99 | }
100 |
101 | destroy() {
102 | this.unmount();
103 |
104 | delete this.element._peity;
105 | }
106 |
107 | static register(type, defaults, grapher) {
108 | Peity.defaults[type] = defaults;
109 | Peity.graphers[type] = grapher;
110 | }
111 | }
112 |
113 | const renderer$2 = (peity) => {
114 | if (!peity.options.delimiter) {
115 | const delimiter = peity.element.innerText.match(/[^0-9\.]/);
116 | peity.options.delimiter = delimiter ? delimiter[0] : ",";
117 | }
118 |
119 | let values = peity.values.map((n) => (n > 0 ? n : 0));
120 |
121 | if (peity.options.delimiter == "/") {
122 | let v1 = values[0];
123 | let v2 = values[1];
124 | values = [v1, Math.max(0, v2 - v1)];
125 | }
126 |
127 | let i = 0;
128 | let length = values.length;
129 | let sum = 0;
130 |
131 | for (; i < length; i++) {
132 | sum += values[i];
133 | }
134 |
135 | if (!sum) {
136 | length = 2;
137 | sum = 1;
138 | values = [0, 1];
139 | }
140 |
141 | let diameter = peity.options.radius * 2;
142 |
143 | const svg = peity.prepare(
144 | peity.options.width || diameter,
145 | peity.options.height || diameter
146 | );
147 |
148 | const width = svg.clientWidth;
149 | const height = svg.clientHeight;
150 | const cx = width / 2;
151 | const cy = height / 2;
152 |
153 | const radius = Math.min(cx, cy);
154 | let innerRadius = peity.options.innerRadius;
155 |
156 | if (peity.type == "donut" && !innerRadius) {
157 | innerRadius = radius * 0.5;
158 | }
159 |
160 | const fill = peity.fill();
161 |
162 | const scale = (value, radius) => {
163 | const radians = (value / sum) * Math.PI * 2 - Math.PI / 2;
164 |
165 | return [radius * Math.cos(radians) + cx, radius * Math.sin(radians) + cy];
166 | };
167 |
168 | let cumulative = 0;
169 |
170 | for (i = 0; i < length; i++) {
171 | const value = values[i];
172 | const portion = value / sum;
173 | let node;
174 |
175 | if (portion == 0) continue;
176 |
177 | if (portion == 1) {
178 | if (innerRadius) {
179 | const x2 = cx - 0.01;
180 | const y1 = cy - radius;
181 | const y2 = cy - innerRadius;
182 |
183 | node = svgElement("path", {
184 | d: [
185 | "M",
186 | cx,
187 | y1,
188 | "A",
189 | radius,
190 | radius,
191 | 0,
192 | 1,
193 | 1,
194 | x2,
195 | y1,
196 | "L",
197 | x2,
198 | y2,
199 | "A",
200 | innerRadius,
201 | innerRadius,
202 | 0,
203 | 1,
204 | 0,
205 | cx,
206 | y2,
207 | ].join(" "),
208 | "data-value": value,
209 | });
210 | } else {
211 | node = svgElement("circle", {
212 | cx: cx,
213 | cy: cy,
214 | "data-value": value,
215 | r: radius,
216 | });
217 | }
218 | } else {
219 | const cumulativePlusValue = cumulative + value;
220 |
221 | let d = ["M"].concat(
222 | scale(cumulative, radius),
223 | "A",
224 | radius,
225 | radius,
226 | 0,
227 | portion > 0.5 ? 1 : 0,
228 | 1,
229 | scale(cumulativePlusValue, radius),
230 | "L"
231 | );
232 |
233 | if (innerRadius) {
234 | d = d.concat(
235 | scale(cumulativePlusValue, innerRadius),
236 | "A",
237 | innerRadius,
238 | innerRadius,
239 | 0,
240 | portion > 0.5 ? 1 : 0,
241 | 0,
242 | scale(cumulative, innerRadius)
243 | );
244 | } else {
245 | d.push(cx, cy);
246 | }
247 |
248 | cumulative += value;
249 |
250 | node = svgElement("path", {
251 | d: d.join(" "),
252 | "data-value": value,
253 | });
254 | }
255 |
256 | node.setAttribute("fill", fill.call(peity, value, i, values));
257 |
258 | svg.append(node);
259 | }
260 | };
261 |
262 | const defaults$2 = {
263 | fill: ["#ff9900", "#fff4dd", "#ffc66e"],
264 | radius: 8,
265 | };
266 |
267 | const renderer$1 = (peity) => {
268 | const values = peity.values;
269 | const max = Math.max.apply(
270 | Math,
271 | peity.options.max == undefined ? values : values.concat(peity.options.max)
272 | );
273 | const min = Math.min.apply(
274 | Math,
275 | peity.options.min == undefined ? values : values.concat(peity.options.min)
276 | );
277 |
278 | const svg = peity.prepare(peity.options.width, peity.options.height);
279 | const width = svg.clientWidth;
280 | const height = svg.clientHeight;
281 | const diff = max - min;
282 | const padding = peity.options.padding;
283 | const fill = peity.fill();
284 |
285 | const xScale = (input) => {
286 | return (input * width) / values.length;
287 | };
288 |
289 | const yScale = (input) => {
290 | return height - (diff ? ((input - min) / diff) * height : 1);
291 | };
292 |
293 | for (var i = 0; i < values.length; i++) {
294 | let x = xScale(i + padding);
295 | let w = xScale(i + 1 - padding) - x;
296 | let value = values[i];
297 | let valueY = yScale(value);
298 | let y1 = valueY;
299 | let y2 = valueY;
300 | let h;
301 |
302 | if (!diff) {
303 | h = 1;
304 | } else if (value < 0) {
305 | y1 = yScale(Math.min(max, 0));
306 | } else {
307 | y2 = yScale(Math.max(min, 0));
308 | }
309 |
310 | h = y2 - y1;
311 |
312 | if (h == 0) {
313 | h = 1;
314 | if (max > 0 && diff) y1--;
315 | }
316 |
317 | svg.append(
318 | svgElement("rect", {
319 | "data-value": value,
320 | fill: fill.call(peity, value, i, values),
321 | x: x,
322 | y: y1,
323 | width: w,
324 | height: h,
325 | })
326 | );
327 | }
328 | };
329 |
330 | const defaults$1 = {
331 | delimiter: ",",
332 | fill: ["#4D89F9"],
333 | height: 16,
334 | min: 0,
335 | padding: 0.1,
336 | width: 32,
337 | };
338 |
339 | const renderer = (peity) => {
340 | const values = peity.values;
341 | if (values.length == 1) values.push(values[0]);
342 | const max = Math.max.apply(
343 | Math,
344 | peity.options.max == undefined ? values : values.concat(peity.options.max)
345 | );
346 | const min = Math.min.apply(
347 | Math,
348 | peity.options.min == undefined ? values : values.concat(peity.options.min)
349 | );
350 |
351 | const svg = peity.prepare(peity.options.width, peity.options.height);
352 | const strokeWidth = peity.options.strokeWidth;
353 | const width = svg.clientWidth;
354 | const height = svg.clientHeight - strokeWidth;
355 | const diff = max - min;
356 |
357 | const xScale = (input) => {
358 | return input * (width / (values.length - 1));
359 | };
360 |
361 | const yScale = (input) => {
362 | let y = height;
363 |
364 | if (diff) {
365 | y -= ((input - min) / diff) * height;
366 | }
367 |
368 | return y + strokeWidth / 2;
369 | };
370 |
371 | let zero = yScale(Math.max(min, 0));
372 | let coords = [0, zero];
373 |
374 | for (var i = 0; i < values.length; i++) {
375 | coords.push(xScale(i), yScale(values[i]));
376 | }
377 |
378 | coords.push(width, zero);
379 |
380 | if (peity.options.fill) {
381 | svg.append(
382 | svgElement("polygon", {
383 | fill: peity.options.fill,
384 | points: coords.join(" "),
385 | })
386 | );
387 | }
388 |
389 | if (strokeWidth) {
390 | svg.append(
391 | svgElement("polyline", {
392 | fill: "none",
393 | points: coords.slice(2, coords.length - 2).join(" "),
394 | stroke: peity.options.stroke,
395 | "stroke-width": strokeWidth,
396 | "stroke-linecap": "square",
397 | })
398 | );
399 | }
400 | };
401 |
402 | const defaults = {
403 | delimiter: ",",
404 | fill: "#c6d9fd",
405 | height: 16,
406 | min: 0,
407 | stroke: "#4d89f9",
408 | strokeWidth: 1,
409 | width: 32,
410 | };
411 |
412 | Peity.register("pie", defaults$2, renderer$2);
413 | Peity.register("donut", defaults$2, renderer$2);
414 | Peity.register("bar", defaults$1, renderer$1);
415 | Peity.register("line", defaults, renderer);
416 |
417 | const peity = function (element, type, options) {
418 | const peity = new Peity(element, type, options);
419 | peity.mount();
420 |
421 | return peity;
422 | };
423 |
424 | peity.defaults = Peity.defaults;
425 | peity.graphers = Peity.graphers;
426 |
427 | function domReady() {
428 | return new Promise((resolve) => {
429 | if (document.readyState == "loading") {
430 | document.addEventListener("DOMContentLoaded", resolve);
431 | } else {
432 | resolve();
433 | }
434 | });
435 | }
436 |
437 | const queryPeityElements = (node) => node.matches("[peity]") ? [node] : Array.from(node.querySelectorAll("[peity]"));
438 |
439 | const getPeityType = (node) => node.getAttribute("peity");
440 |
441 | const elementFromNode = (node) => {
442 | if (node.nodeType == Node.ELEMENT_NODE) {
443 | return node;
444 | }
445 | };
446 |
447 | class Watcher {
448 | constructor() {
449 | this.elements = new Set();
450 | }
451 |
452 | async start() {
453 | await domReady();
454 |
455 | this.element = document.documentElement;
456 |
457 | const observer = new MutationObserver(this.callback.bind(this));
458 | observer.observe(this.element, {
459 | childList: true,
460 | attributes: true,
461 | subtree: true,
462 | });
463 |
464 | this.processNodeAdded(this.element);
465 | };
466 |
467 | processNodeAdded(node) {
468 | for (const element of queryPeityElements(node)) {
469 | if (!this.elements.has(element)) {
470 | this.elements.add(element);
471 | peity(element, getPeityType(element));
472 | }
473 | }
474 | };
475 |
476 | processNodeRemoved(node) {
477 | const matches = new Set(queryPeityElements(node));
478 |
479 | for (const element of this.elements) {
480 | if(matches.has(element)) {
481 | this.elements.delete(element);
482 | if (element._peity) {
483 | element._peity.destroy();
484 | }
485 | }
486 | }
487 | };
488 |
489 | processAttributeChanged(mutation) {
490 | const node = mutation.target;
491 | if(!this.elementIsActive(node)) return;
492 |
493 | switch (mutation.attributeName) {
494 | case "peity":
495 | const type = getPeityType(node);
496 | if (type) {
497 | if (!this.elements.has(node)) {
498 | this.elements.add(node);
499 | }
500 | peity(node, type);
501 | } else {
502 | if (this.elements.has(node)) {
503 | this.elements.delete(node);
504 | if (node._peity) {
505 | node._peity.destroy();
506 | }
507 | }
508 | }
509 | break;
510 | case "data-peity":
511 | if (node._peity) {
512 | peity(node, node._peity.type);
513 | }
514 | break;
515 | }
516 | }
517 |
518 | callback(mutationList, observer) {
519 | mutationList.forEach((mutation) => {
520 | switch (mutation.type) {
521 | case "childList":
522 | for (const node of Array.from(mutation.removedNodes)) {
523 | const element = elementFromNode(node);
524 | if (element) {
525 | this.processNodeRemoved(element);
526 | }
527 | }
528 | for (const node of Array.from(mutation.addedNodes)) {
529 | const element = elementFromNode(node);
530 | if (element && this.elementIsActive(element)) {
531 | this.processNodeAdded(element);
532 | }
533 | }
534 | break;
535 | case "attributes":
536 | this.processAttributeChanged(mutation);
537 | break;
538 | }
539 | });
540 | };
541 |
542 | elementIsActive(element) {
543 | return this.element.contains(element);
544 | }
545 | }
546 |
547 | new Watcher().start();
548 |
549 | export { peity as default };
550 |
--------------------------------------------------------------------------------
/app/assets/javascripts/peity-vanilla-rails.min.js:
--------------------------------------------------------------------------------
1 | /*!
2 | Peity Vanila Rails 0.2.2
3 | Copyright © 2022 RailsJazz
4 | https://railsjazz.com
5 | */
6 | var peity=function(){"use strict";
7 | /*!
8 | Peity Vanila JS 0.0.8
9 | Copyright © 2022 RailsJazz
10 | https://railsjazz.com
11 | */const t=t=>null!==t&&"function"==typeof t&&!!t.apply,e=(t,e)=>{const i=document.createElementNS("http://www.w3.org/2000/svg",t);for(var s in e)i.setAttribute(s,e[s]);return i},i="createElementNS"in document&&e("svg",{}).createSVGRect();class s{static defaults={};static graphers={};constructor(t,e,i={}){this.element=t,this.type=e,this.options=Object.assign({},s.defaults[this.type],JSON.parse(t.dataset.peity||"{}"),i),this.element._peity&&this.element._peity.destroy(),this.element._peity=this}draw(){const e=this.options;s.graphers[this.type](this),t(e.after)&&e.after.call(this,e)}fill(){var e=this.options.fill;return t(e)?e:function(t,i){return e[i%e.length]}}prepare(t,i){return this.svg||(this.element.style.display="none",this.element.after(this.svg=e("svg",{class:"peity"}))),this.svg.innerHTML="",this.svg.setAttribute("width",t),this.svg.setAttribute("height",i),this.svg}get values(){return this.element.innerText.split(this.options.delimiter).map((t=>parseFloat(t)))}mount(){i&&(this.element.addEventListener("DOMSubtreeModified",this.draw.bind(this)),this.draw(),this.mounted=!0)}unmount(){this.element.removeEventListener("DOMSubtreeModified",this.draw),this.svg.remove(),this.mounted=!1}destroy(){this.unmount(),delete this.element._peity}static register(t,e,i){s.defaults[t]=e,s.graphers[t]=i}}const n=t=>{if(!t.options.delimiter){const e=t.element.innerText.match(/[^0-9\.]/);t.options.delimiter=e?e[0]:","}let i=t.values.map((t=>t>0?t:0));if("/"==t.options.delimiter){let t=i[0],e=i[1];i=[t,Math.max(0,e-t)]}let s=0,n=i.length,o=0;for(;s{const i=t/o*Math.PI*2-Math.PI/2;return[e*Math.cos(i)+d,e*Math.sin(i)+p]};let g=0;for(s=0;s.5?1:0,1,f(t,c),"L");m?i=i.concat(f(t,m),"A",m,m,0,a>.5?1:0,0,f(g,m)):i.push(d,p),g+=n,l=e("path",{d:i.join(" "),"data-value":n})}l.setAttribute("fill",u.call(t,n,s,i)),r.append(l)}}},o={fill:["#ff9900","#fff4dd","#ffc66e"],radius:8};s.register("pie",o,n),s.register("donut",o,n),s.register("bar",{delimiter:",",fill:["#4D89F9"],height:16,min:0,padding:.1,width:32},(t=>{const i=t.values,s=Math.max.apply(Math,null==t.options.max?i:i.concat(t.options.max)),n=Math.min.apply(Math,null==t.options.min?i:i.concat(t.options.min)),o=t.prepare(t.options.width,t.options.height),a=o.clientWidth,r=o.clientHeight,l=s-n,h=t.options.padding,d=t.fill(),p=t=>t*a/i.length,c=t=>r-(l?(t-n)/l*r:1);for(var m=0;m0&&l&&y--),o.append(e("rect",{"data-value":f,fill:d.call(t,f,m,i),x:r,y:y,width:u,height:a}))}})),s.register("line",{delimiter:",",fill:"#c6d9fd",height:16,min:0,stroke:"#4d89f9",strokeWidth:1,width:32},(t=>{const i=t.values;1==i.length&&i.push(i[0]);const s=Math.max.apply(Math,null==t.options.max?i:i.concat(t.options.max)),n=Math.min.apply(Math,null==t.options.min?i:i.concat(t.options.min)),o=t.prepare(t.options.width,t.options.height),a=t.options.strokeWidth,r=o.clientWidth,l=o.clientHeight-a,h=s-n,d=t=>{let e=l;return h&&(e-=(t-n)/h*l),e+a/2};let p=d(Math.max(n,0)),c=[0,p];for(var m=0;mt.matches("[peity]")?[t]:Array.from(t.querySelectorAll("[peity]")),l=t=>t.getAttribute("peity"),h=t=>{if(t.nodeType==Node.ELEMENT_NODE)return t};return(new class{constructor(){this.elements=new Set}async start(){await new Promise((t=>{"loading"==document.readyState?document.addEventListener("DOMContentLoaded",t):t()})),this.element=document.documentElement;new MutationObserver(this.callback.bind(this)).observe(this.element,{childList:!0,attributes:!0,subtree:!0}),this.processNodeAdded(this.element)}processNodeAdded(t){for(const e of r(t))this.elements.has(e)||(this.elements.add(e),a(e,l(e)))}processNodeRemoved(t){const e=new Set(r(t));for(const t of this.elements)e.has(t)&&(this.elements.delete(t),t._peity&&t._peity.destroy())}processAttributeChanged(t){const e=t.target;if(this.elementIsActive(e))switch(t.attributeName){case"peity":const t=l(e);t?(this.elements.has(e)||this.elements.add(e),a(e,t)):this.elements.has(e)&&(this.elements.delete(e),e._peity&&e._peity.destroy());break;case"data-peity":e._peity&&a(e,e._peity.type)}}callback(t,e){t.forEach((t=>{switch(t.type){case"childList":for(const e of Array.from(t.removedNodes)){const t=h(e);t&&this.processNodeRemoved(t)}for(const e of Array.from(t.addedNodes)){const t=h(e);t&&this.elementIsActive(t)&&this.processNodeAdded(t)}break;case"attributes":this.processAttributeChanged(t)}}))}elementIsActive(t){return this.element.contains(t)}}).start(),a}();
12 |
--------------------------------------------------------------------------------
/app/assets/javascripts/peity-vanilla.esm.js:
--------------------------------------------------------------------------------
1 | /*!
2 | Peity Vanila JS 0.0.8
3 | Copyright © 2022 RailsJazz
4 | https://railsjazz.com
5 | */
6 |
7 | const isFunction = (o) =>
8 | o !== null && typeof o === "function" && !!o.apply;
9 |
10 | const svgElement = (tag, attrs) => {
11 | const element = document.createElementNS("http://www.w3.org/2000/svg", tag);
12 | for (var attr in attrs) {
13 | element.setAttribute(attr, attrs[attr]);
14 | }
15 | return element;
16 | };
17 |
18 | const svgSupported =
19 | "createElementNS" in document && svgElement("svg", {}).createSVGRect();
20 |
21 | class Peity {
22 | static defaults = {};
23 | static graphers = {};
24 |
25 | constructor(element, type, options = {}) {
26 | this.element = element;
27 | this.type = type;
28 | this.options = Object.assign(
29 | {},
30 | Peity.defaults[this.type],
31 | JSON.parse(element.dataset["peity"] || "{}"),
32 | options
33 | );
34 |
35 | if(this.element._peity) {
36 | this.element._peity.destroy();
37 | }
38 | this.element._peity = this;
39 | }
40 |
41 | draw() {
42 | const options = this.options;
43 | Peity.graphers[this.type](this);
44 | if (isFunction(options.after)) options.after.call(this, options);
45 | }
46 |
47 | fill() {
48 | var fill = this.options.fill;
49 |
50 | return isFunction(fill)
51 | ? fill
52 | : function (_, i) {
53 | return fill[i % fill.length];
54 | };
55 | }
56 |
57 | prepare(width, height) {
58 | if (!this.svg) {
59 | this.element.style.display = "none";
60 | this.element.after(
61 | (this.svg = svgElement("svg", {
62 | class: "peity",
63 | }))
64 | );
65 | }
66 |
67 | this.svg.innerHTML = "";
68 | this.svg.setAttribute("width", width);
69 | this.svg.setAttribute("height", height);
70 |
71 | return this.svg;
72 | }
73 |
74 | get values() {
75 | return this.element.innerText
76 | .split(this.options.delimiter)
77 | .map((value) => parseFloat(value));
78 | }
79 |
80 | mount() {
81 | if (!svgSupported) return;
82 |
83 | this.element.addEventListener("DOMSubtreeModified", this.draw.bind(this));
84 | this.draw();
85 |
86 | this.mounted = true;
87 | }
88 |
89 | unmount() {
90 | this.element.removeEventListener("DOMSubtreeModified", this.draw);
91 | this.svg.remove();
92 | this.mounted = false;
93 | }
94 |
95 | destroy() {
96 | this.unmount();
97 |
98 | delete this.element._peity;
99 | }
100 |
101 | static register(type, defaults, grapher) {
102 | Peity.defaults[type] = defaults;
103 | Peity.graphers[type] = grapher;
104 | }
105 | }
106 |
107 | const renderer$2 = (peity) => {
108 | if (!peity.options.delimiter) {
109 | const delimiter = peity.element.innerText.match(/[^0-9\.]/);
110 | peity.options.delimiter = delimiter ? delimiter[0] : ",";
111 | }
112 |
113 | let values = peity.values.map((n) => (n > 0 ? n : 0));
114 |
115 | if (peity.options.delimiter == "/") {
116 | let v1 = values[0];
117 | let v2 = values[1];
118 | values = [v1, Math.max(0, v2 - v1)];
119 | }
120 |
121 | let i = 0;
122 | let length = values.length;
123 | let sum = 0;
124 |
125 | for (; i < length; i++) {
126 | sum += values[i];
127 | }
128 |
129 | if (!sum) {
130 | length = 2;
131 | sum = 1;
132 | values = [0, 1];
133 | }
134 |
135 | let diameter = peity.options.radius * 2;
136 |
137 | const svg = peity.prepare(
138 | peity.options.width || diameter,
139 | peity.options.height || diameter
140 | );
141 |
142 | const width = svg.clientWidth;
143 | const height = svg.clientHeight;
144 | const cx = width / 2;
145 | const cy = height / 2;
146 |
147 | const radius = Math.min(cx, cy);
148 | let innerRadius = peity.options.innerRadius;
149 |
150 | if (peity.type == "donut" && !innerRadius) {
151 | innerRadius = radius * 0.5;
152 | }
153 |
154 | const fill = peity.fill();
155 |
156 | const scale = (value, radius) => {
157 | const radians = (value / sum) * Math.PI * 2 - Math.PI / 2;
158 |
159 | return [radius * Math.cos(radians) + cx, radius * Math.sin(radians) + cy];
160 | };
161 |
162 | let cumulative = 0;
163 |
164 | for (i = 0; i < length; i++) {
165 | const value = values[i];
166 | const portion = value / sum;
167 | let node;
168 |
169 | if (portion == 0) continue;
170 |
171 | if (portion == 1) {
172 | if (innerRadius) {
173 | const x2 = cx - 0.01;
174 | const y1 = cy - radius;
175 | const y2 = cy - innerRadius;
176 |
177 | node = svgElement("path", {
178 | d: [
179 | "M",
180 | cx,
181 | y1,
182 | "A",
183 | radius,
184 | radius,
185 | 0,
186 | 1,
187 | 1,
188 | x2,
189 | y1,
190 | "L",
191 | x2,
192 | y2,
193 | "A",
194 | innerRadius,
195 | innerRadius,
196 | 0,
197 | 1,
198 | 0,
199 | cx,
200 | y2,
201 | ].join(" "),
202 | "data-value": value,
203 | });
204 | } else {
205 | node = svgElement("circle", {
206 | cx: cx,
207 | cy: cy,
208 | "data-value": value,
209 | r: radius,
210 | });
211 | }
212 | } else {
213 | const cumulativePlusValue = cumulative + value;
214 |
215 | let d = ["M"].concat(
216 | scale(cumulative, radius),
217 | "A",
218 | radius,
219 | radius,
220 | 0,
221 | portion > 0.5 ? 1 : 0,
222 | 1,
223 | scale(cumulativePlusValue, radius),
224 | "L"
225 | );
226 |
227 | if (innerRadius) {
228 | d = d.concat(
229 | scale(cumulativePlusValue, innerRadius),
230 | "A",
231 | innerRadius,
232 | innerRadius,
233 | 0,
234 | portion > 0.5 ? 1 : 0,
235 | 0,
236 | scale(cumulative, innerRadius)
237 | );
238 | } else {
239 | d.push(cx, cy);
240 | }
241 |
242 | cumulative += value;
243 |
244 | node = svgElement("path", {
245 | d: d.join(" "),
246 | "data-value": value,
247 | });
248 | }
249 |
250 | node.setAttribute("fill", fill.call(peity, value, i, values));
251 |
252 | svg.append(node);
253 | }
254 | };
255 |
256 | const defaults$2 = {
257 | fill: ["#ff9900", "#fff4dd", "#ffc66e"],
258 | radius: 8,
259 | };
260 |
261 | const renderer$1 = (peity) => {
262 | const values = peity.values;
263 | const max = Math.max.apply(
264 | Math,
265 | peity.options.max == undefined ? values : values.concat(peity.options.max)
266 | );
267 | const min = Math.min.apply(
268 | Math,
269 | peity.options.min == undefined ? values : values.concat(peity.options.min)
270 | );
271 |
272 | const svg = peity.prepare(peity.options.width, peity.options.height);
273 | const width = svg.clientWidth;
274 | const height = svg.clientHeight;
275 | const diff = max - min;
276 | const padding = peity.options.padding;
277 | const fill = peity.fill();
278 |
279 | const xScale = (input) => {
280 | return (input * width) / values.length;
281 | };
282 |
283 | const yScale = (input) => {
284 | return height - (diff ? ((input - min) / diff) * height : 1);
285 | };
286 |
287 | for (var i = 0; i < values.length; i++) {
288 | let x = xScale(i + padding);
289 | let w = xScale(i + 1 - padding) - x;
290 | let value = values[i];
291 | let valueY = yScale(value);
292 | let y1 = valueY;
293 | let y2 = valueY;
294 | let h;
295 |
296 | if (!diff) {
297 | h = 1;
298 | } else if (value < 0) {
299 | y1 = yScale(Math.min(max, 0));
300 | } else {
301 | y2 = yScale(Math.max(min, 0));
302 | }
303 |
304 | h = y2 - y1;
305 |
306 | if (h == 0) {
307 | h = 1;
308 | if (max > 0 && diff) y1--;
309 | }
310 |
311 | svg.append(
312 | svgElement("rect", {
313 | "data-value": value,
314 | fill: fill.call(peity, value, i, values),
315 | x: x,
316 | y: y1,
317 | width: w,
318 | height: h,
319 | })
320 | );
321 | }
322 | };
323 |
324 | const defaults$1 = {
325 | delimiter: ",",
326 | fill: ["#4D89F9"],
327 | height: 16,
328 | min: 0,
329 | padding: 0.1,
330 | width: 32,
331 | };
332 |
333 | const renderer = (peity) => {
334 | const values = peity.values;
335 | if (values.length == 1) values.push(values[0]);
336 | const max = Math.max.apply(
337 | Math,
338 | peity.options.max == undefined ? values : values.concat(peity.options.max)
339 | );
340 | const min = Math.min.apply(
341 | Math,
342 | peity.options.min == undefined ? values : values.concat(peity.options.min)
343 | );
344 |
345 | const svg = peity.prepare(peity.options.width, peity.options.height);
346 | const strokeWidth = peity.options.strokeWidth;
347 | const width = svg.clientWidth;
348 | const height = svg.clientHeight - strokeWidth;
349 | const diff = max - min;
350 |
351 | const xScale = (input) => {
352 | return input * (width / (values.length - 1));
353 | };
354 |
355 | const yScale = (input) => {
356 | let y = height;
357 |
358 | if (diff) {
359 | y -= ((input - min) / diff) * height;
360 | }
361 |
362 | return y + strokeWidth / 2;
363 | };
364 |
365 | let zero = yScale(Math.max(min, 0));
366 | let coords = [0, zero];
367 |
368 | for (var i = 0; i < values.length; i++) {
369 | coords.push(xScale(i), yScale(values[i]));
370 | }
371 |
372 | coords.push(width, zero);
373 |
374 | if (peity.options.fill) {
375 | svg.append(
376 | svgElement("polygon", {
377 | fill: peity.options.fill,
378 | points: coords.join(" "),
379 | })
380 | );
381 | }
382 |
383 | if (strokeWidth) {
384 | svg.append(
385 | svgElement("polyline", {
386 | fill: "none",
387 | points: coords.slice(2, coords.length - 2).join(" "),
388 | stroke: peity.options.stroke,
389 | "stroke-width": strokeWidth,
390 | "stroke-linecap": "square",
391 | })
392 | );
393 | }
394 | };
395 |
396 | const defaults = {
397 | delimiter: ",",
398 | fill: "#c6d9fd",
399 | height: 16,
400 | min: 0,
401 | stroke: "#4d89f9",
402 | strokeWidth: 1,
403 | width: 32,
404 | };
405 |
406 | Peity.register("pie", defaults$2, renderer$2);
407 | Peity.register("donut", defaults$2, renderer$2);
408 | Peity.register("bar", defaults$1, renderer$1);
409 | Peity.register("line", defaults, renderer);
410 |
411 | const peity = function (element, type, options) {
412 | const peity = new Peity(element, type, options);
413 | peity.mount();
414 |
415 | return peity;
416 | };
417 |
418 | peity.defaults = Peity.defaults;
419 | peity.graphers = Peity.graphers;
420 |
421 | export { peity as default };
422 |
--------------------------------------------------------------------------------
/app/assets/javascripts/peity-vanilla.min.js:
--------------------------------------------------------------------------------
1 | var peity=function(){"use strict";
2 | /*!
3 | Peity Vanila JS 0.0.8
4 | Copyright © 2022 RailsJazz
5 | https://railsjazz.com
6 | */const t=t=>null!==t&&"function"==typeof t&&!!t.apply,e=(t,e)=>{const i=document.createElementNS("http://www.w3.org/2000/svg",t);for(var n in e)i.setAttribute(n,e[n]);return i},i="createElementNS"in document&&e("svg",{}).createSVGRect();class n{static defaults={};static graphers={};constructor(t,e,i={}){this.element=t,this.type=e,this.options=Object.assign({},n.defaults[this.type],JSON.parse(t.dataset.peity||"{}"),i),this.element._peity&&this.element._peity.destroy(),this.element._peity=this}draw(){const e=this.options;n.graphers[this.type](this),t(e.after)&&e.after.call(this,e)}fill(){var e=this.options.fill;return t(e)?e:function(t,i){return e[i%e.length]}}prepare(t,i){return this.svg||(this.element.style.display="none",this.element.after(this.svg=e("svg",{class:"peity"}))),this.svg.innerHTML="",this.svg.setAttribute("width",t),this.svg.setAttribute("height",i),this.svg}get values(){return this.element.innerText.split(this.options.delimiter).map((t=>parseFloat(t)))}mount(){i&&(this.element.addEventListener("DOMSubtreeModified",this.draw.bind(this)),this.draw(),this.mounted=!0)}unmount(){this.element.removeEventListener("DOMSubtreeModified",this.draw),this.svg.remove(),this.mounted=!1}destroy(){this.unmount(),delete this.element._peity}static register(t,e,i){n.defaults[t]=e,n.graphers[t]=i}}const s=t=>{if(!t.options.delimiter){const e=t.element.innerText.match(/[^0-9\.]/);t.options.delimiter=e?e[0]:","}let i=t.values.map((t=>t>0?t:0));if("/"==t.options.delimiter){let t=i[0],e=i[1];i=[t,Math.max(0,e-t)]}let n=0,s=i.length,o=0;for(;n{const i=t/o*Math.PI*2-Math.PI/2;return[e*Math.cos(i)+p,e*Math.sin(i)+d]};let g=0;for(n=0;n.5?1:0,1,f(t,c),"L");u?i=i.concat(f(t,u),"A",u,u,0,l>.5?1:0,0,f(g,u)):i.push(p,d),g+=s,r=e("path",{d:i.join(" "),"data-value":s})}r.setAttribute("fill",m.call(t,s,n,i)),a.append(r)}}},o={fill:["#ff9900","#fff4dd","#ffc66e"],radius:8};n.register("pie",o,s),n.register("donut",o,s),n.register("bar",{delimiter:",",fill:["#4D89F9"],height:16,min:0,padding:.1,width:32},(t=>{const i=t.values,n=Math.max.apply(Math,null==t.options.max?i:i.concat(t.options.max)),s=Math.min.apply(Math,null==t.options.min?i:i.concat(t.options.min)),o=t.prepare(t.options.width,t.options.height),l=o.clientWidth,a=o.clientHeight,r=n-s,h=t.options.padding,p=t.fill(),d=t=>t*l/i.length,c=t=>a-(r?(t-s)/r*a:1);for(var u=0;u0&&r&&v--),o.append(e("rect",{"data-value":f,fill:p.call(t,f,u,i),x:a,y:v,width:m,height:l}))}})),n.register("line",{delimiter:",",fill:"#c6d9fd",height:16,min:0,stroke:"#4d89f9",strokeWidth:1,width:32},(t=>{const i=t.values;1==i.length&&i.push(i[0]);const n=Math.max.apply(Math,null==t.options.max?i:i.concat(t.options.max)),s=Math.min.apply(Math,null==t.options.min?i:i.concat(t.options.min)),o=t.prepare(t.options.width,t.options.height),l=t.options.strokeWidth,a=o.clientWidth,r=o.clientHeight-l,h=n-s,p=t=>{let e=r;return h&&(e-=(t-s)/h*r),e+l/2};let d=p(Math.max(s,0)),c=[0,d];for(var u=0;u {
5 | if (document.readyState == "loading") {
6 | document.addEventListener("DOMContentLoaded", resolve);
7 | } else {
8 | resolve();
9 | }
10 | });
11 | }
12 |
13 | const queryPeityElements = (node) => node.matches("[peity]") ? [node] : Array.from(node.querySelectorAll("[peity]"));
14 |
15 | const getPeityType = (node) => node.getAttribute("peity");
16 |
17 | const elementFromNode = (node) => {
18 | if (node.nodeType == Node.ELEMENT_NODE) {
19 | return node;
20 | }
21 | };
22 |
23 | class Watcher {
24 | constructor() {
25 | this.elements = new Set();
26 | }
27 |
28 | async start() {
29 | await domReady();
30 |
31 | this.element = document.documentElement;
32 |
33 | const observer = new MutationObserver(this.callback.bind(this));
34 | observer.observe(this.element, {
35 | childList: true,
36 | attributes: true,
37 | subtree: true,
38 | });
39 |
40 | this.processNodeAdded(this.element);
41 | };
42 |
43 | processNodeAdded(node) {
44 | for (const element of queryPeityElements(node)) {
45 | if (!this.elements.has(element)) {
46 | this.elements.add(element)
47 | peity(element, getPeityType(element));
48 | }
49 | }
50 | };
51 |
52 | processNodeRemoved(node) {
53 | const matches = new Set(queryPeityElements(node));
54 |
55 | for (const element of this.elements) {
56 | if(matches.has(element)) {
57 | this.elements.delete(element);
58 | if (element._peity) {
59 | element._peity.destroy();
60 | }
61 | }
62 | }
63 | };
64 |
65 | processAttributeChanged(mutation) {
66 | const node = mutation.target;
67 | if(!this.elementIsActive(node)) return;
68 |
69 | switch (mutation.attributeName) {
70 | case "peity":
71 | const type = getPeityType(node);
72 | if (type) {
73 | if (!this.elements.has(node)) {
74 | this.elements.add(node)
75 | }
76 | peity(node, type);
77 | } else {
78 | if (this.elements.has(node)) {
79 | this.elements.delete(node);
80 | if (node._peity) {
81 | node._peity.destroy();
82 | }
83 | }
84 | }
85 | break;
86 | case "data-peity":
87 | if (node._peity) {
88 | peity(node, node._peity.type);
89 | }
90 | break;
91 | }
92 | }
93 |
94 | callback(mutationList, observer) {
95 | mutationList.forEach((mutation) => {
96 | switch (mutation.type) {
97 | case "childList":
98 | for (const node of Array.from(mutation.removedNodes)) {
99 | const element = elementFromNode(node);
100 | if (element) {
101 | this.processNodeRemoved(element);
102 | }
103 | }
104 | for (const node of Array.from(mutation.addedNodes)) {
105 | const element = elementFromNode(node);
106 | if (element && this.elementIsActive(element)) {
107 | this.processNodeAdded(element);
108 | }
109 | }
110 | break;
111 | case "attributes":
112 | this.processAttributeChanged(mutation);
113 | break;
114 | }
115 | });
116 | };
117 |
118 | elementIsActive(element) {
119 | return this.element.contains(element);
120 | }
121 | }
122 |
123 | new Watcher().start();
124 |
125 | export * from "peity-vanilla";
126 | export default peity;
127 |
--------------------------------------------------------------------------------
/javascript/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "peity_vanilla_rails",
3 | "version": "0.2.2",
4 | "main": "index.js",
5 | "repository": "https://github.com/railsjazz/peity_vanilla_rails.git",
6 | "author": "www.railsjazz.com",
7 | "license": "MIT",
8 | "scripts": {
9 | "build": "rollup -c",
10 | "watch": "rollup -wc"
11 | },
12 | "dependencies": {
13 | "peity-vanilla": "^0.0.8"
14 | },
15 | "devDependencies": {
16 | "@rollup/plugin-node-resolve": "^13.3.0",
17 | "rollup": "^2.71.1",
18 | "rollup-plugin-terser": "^7.0.2"
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/javascript/peity_vanilla.js:
--------------------------------------------------------------------------------
1 | import peity from "peity-vanilla";
2 |
3 | export * from "peity-vanilla";
4 | export default peity;
5 |
--------------------------------------------------------------------------------
/javascript/rollup.config.js:
--------------------------------------------------------------------------------
1 | import resolve from "@rollup/plugin-node-resolve";
2 | import { version } from "./package.json";
3 | import { terser } from 'rollup-plugin-terser';
4 |
5 | const year = new Date().getFullYear();
6 |
7 | const banner = `/*!
8 | Peity Vanila Rails ${version}
9 | Copyright © ${year} RailsJazz
10 | https://railsjazz.com
11 | */
12 | `;
13 |
14 | export default [
15 | {
16 | input: "index.js",
17 | output: [
18 | {
19 | file: "../app/assets/javascripts/peity-vanilla-rails.esm.js",
20 | format: "es",
21 | banner,
22 | },
23 | {
24 | name: "peity",
25 | file: "../app/assets/javascripts/peity-vanilla-rails.min.js",
26 | format: "iife",
27 | banner: banner,
28 | plugins: [terser()]
29 | },
30 | ],
31 | plugins: [
32 | resolve(),
33 | ],
34 | watch: {
35 | include: "**",
36 | },
37 | },
38 | {
39 | input: "peity_vanilla.js",
40 | output: [
41 | {
42 | file: "../app/assets/javascripts/peity-vanilla.esm.js",
43 | format: "es",
44 | },
45 | {
46 | name: "peity",
47 | file: "../app/assets/javascripts/peity-vanilla.min.js",
48 | format: "iife",
49 | plugins: [terser()]
50 | },
51 | ],
52 | plugins: [
53 | resolve(),
54 | ],
55 | watch: {
56 | include: "**",
57 | },
58 | },
59 | ];
60 |
--------------------------------------------------------------------------------
/lib/peity_vanilla_rails.rb:
--------------------------------------------------------------------------------
1 | require "peity_vanilla_rails/version"
2 | require "peity_vanilla_rails/helpers"
3 | require "peity_vanilla_rails/engine"
4 |
5 | module PeityVanillaRails
6 | end
7 |
--------------------------------------------------------------------------------
/lib/peity_vanilla_rails/engine.rb:
--------------------------------------------------------------------------------
1 | module PeityVanillaRails
2 | class Railtie < ::Rails::Engine
3 | PRECOMPILE_ASSETS = %w( peity-vanilla-rails.esm.js peity-vanilla.esm.js )
4 | initializer "peity_vanilla_rails.importmap", before: "importmap" do |app|
5 | if Rails.application.respond_to?(:importmap)
6 | app.config.assets.precompile += PRECOMPILE_ASSETS
7 | app.config.importmap.paths << root.join("config/importmap.rb")
8 | end
9 | end
10 |
11 | initializer 'peity_vanilla_rails.helpers', before: :load_config_initializers do
12 | ActiveSupport.on_load :action_view do
13 | include PeityVanillaRails::Helpers
14 | end
15 | end
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/lib/peity_vanilla_rails/helpers.rb:
--------------------------------------------------------------------------------
1 | module PeityVanillaRails
2 | module Helpers
3 |
4 | def peity_line_chart(data, id: nil, options: {})
5 | peity_chart(data, "line", ",", id: id, options: options)
6 | end
7 |
8 | def peity_bar_chart(data, id: nil, options: {})
9 | peity_chart(data, "bar", ",", id: id, options: options)
10 | end
11 |
12 | def peity_pie_chart(data, id: nil, options: {})
13 | peity_chart(data, "pie", "/", id: id, options: options)
14 | end
15 |
16 | def peity_donut_chart(data, id: nil, options: {})
17 | peity_chart(data, "donut", "/", id: id, options: options)
18 | end
19 |
20 | def peity_chart(value, type, delimiter, id: nil, options: {})
21 | value = value.is_a?(Array) ? value.join(delimiter) : value
22 | tag.span(id: id, class: 'peity_charts', peity: type, style: 'display: none;', data: { peity: options.to_json }) do
23 | value
24 | end
25 | end
26 |
27 | end
28 | end
29 |
--------------------------------------------------------------------------------
/lib/peity_vanilla_rails/version.rb:
--------------------------------------------------------------------------------
1 | module PeityVanillaRails
2 | VERSION = "0.2.2"
3 | end
4 |
--------------------------------------------------------------------------------
/lib/tasks/peity_vanilla_rails_tasks.rake:
--------------------------------------------------------------------------------
1 | # desc "Explaining what the task does"
2 | # task :peity_vanilla_rails do
3 | # # Task goes here
4 | # end
5 |
--------------------------------------------------------------------------------
/peity_vanilla_rails.gemspec:
--------------------------------------------------------------------------------
1 | require_relative "lib/peity_vanilla_rails/version"
2 |
3 | Gem::Specification.new do |spec|
4 | spec.name = "peity_vanilla_rails"
5 | spec.version = PeityVanillaRails::VERSION
6 | spec.authors = ["Igor Kasyanchuk", "Liubomyr Manastyretskyi"]
7 | spec.email = ["igorkasyanchuk@gmail.com", "manastyretskyi@gmail.com"]
8 | spec.homepage = "https://github.com/railsjazz/peity_vanilla_rails"
9 | spec.summary = "Sparklines are small but intense charts."
10 | spec.description = "Sparklines are small but intense charts."
11 | spec.license = "MIT"
12 |
13 | spec.metadata["homepage_uri"] = spec.homepage
14 | spec.metadata["source_code_uri"] = "https://github.com/railsjazz/peity_vanilla_rails"
15 | spec.metadata["changelog_uri"] = "https://github.com/railsjazz/peity_vanilla_rails/releases"
16 |
17 | spec.files = Dir.chdir(File.expand_path(__dir__)) do
18 | Dir["{app,config,db,lib}/**/*", "MIT-LICENSE", "Rakefile", "README.md"]
19 | end
20 |
21 | spec.add_dependency "rails"
22 | spec.add_development_dependency "pry"
23 | spec.add_development_dependency "puma"
24 | spec.add_development_dependency "sprockets-rails"
25 | end
26 |
--------------------------------------------------------------------------------
/test/dummy/Rakefile:
--------------------------------------------------------------------------------
1 | # Add your own tasks in files placed in lib/tasks ending in .rake,
2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
3 |
4 | require_relative "config/application"
5 |
6 | Rails.application.load_tasks
7 |
--------------------------------------------------------------------------------
/test/dummy/app/assets/config/manifest.js:
--------------------------------------------------------------------------------
1 | //= link_tree ../images
2 | //= link_directory ../javascripts .js
3 | //= link_directory ../stylesheets .css
--------------------------------------------------------------------------------
/test/dummy/app/assets/images/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/railsjazz/peity_vanilla_rails/726847a3b8cc0daa5215e8076331a4d1e18cd2de/test/dummy/app/assets/images/.keep
--------------------------------------------------------------------------------
/test/dummy/app/assets/javascripts/application.js:
--------------------------------------------------------------------------------
1 | //= require peity-vanilla-rails.min.js
2 |
--------------------------------------------------------------------------------
/test/dummy/app/assets/stylesheets/application.css:
--------------------------------------------------------------------------------
1 | /* Application styles */
2 | body {
3 | font-family: 'Lucida Sans', 'Lucida Sans Regular', 'Lucida Grande', 'Lucida Sans Unicode', Geneva, Verdana, sans-serif;
4 | }
--------------------------------------------------------------------------------
/test/dummy/app/channels/application_cable/channel.rb:
--------------------------------------------------------------------------------
1 | module ApplicationCable
2 | class Channel < ActionCable::Channel::Base
3 | end
4 | end
5 |
--------------------------------------------------------------------------------
/test/dummy/app/channels/application_cable/connection.rb:
--------------------------------------------------------------------------------
1 | module ApplicationCable
2 | class Connection < ActionCable::Connection::Base
3 | end
4 | end
5 |
--------------------------------------------------------------------------------
/test/dummy/app/controllers/application_controller.rb:
--------------------------------------------------------------------------------
1 | class ApplicationController < ActionController::Base
2 | end
3 |
--------------------------------------------------------------------------------
/test/dummy/app/controllers/concerns/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/railsjazz/peity_vanilla_rails/726847a3b8cc0daa5215e8076331a4d1e18cd2de/test/dummy/app/controllers/concerns/.keep
--------------------------------------------------------------------------------
/test/dummy/app/controllers/home_controller.rb:
--------------------------------------------------------------------------------
1 | class HomeController < ApplicationController
2 | def index
3 | end
4 | end
5 |
--------------------------------------------------------------------------------
/test/dummy/app/helpers/application_helper.rb:
--------------------------------------------------------------------------------
1 | module ApplicationHelper
2 | end
3 |
--------------------------------------------------------------------------------
/test/dummy/app/helpers/home_helper.rb:
--------------------------------------------------------------------------------
1 | module HomeHelper
2 | end
3 |
--------------------------------------------------------------------------------
/test/dummy/app/jobs/application_job.rb:
--------------------------------------------------------------------------------
1 | class ApplicationJob < ActiveJob::Base
2 | # Automatically retry jobs that encountered a deadlock
3 | # retry_on ActiveRecord::Deadlocked
4 |
5 | # Most jobs are safe to ignore if the underlying records are no longer available
6 | # discard_on ActiveJob::DeserializationError
7 | end
8 |
--------------------------------------------------------------------------------
/test/dummy/app/mailers/application_mailer.rb:
--------------------------------------------------------------------------------
1 | class ApplicationMailer < ActionMailer::Base
2 | default from: "from@example.com"
3 | layout "mailer"
4 | end
5 |
--------------------------------------------------------------------------------
/test/dummy/app/models/application_record.rb:
--------------------------------------------------------------------------------
1 | class ApplicationRecord < ActiveRecord::Base
2 | primary_abstract_class
3 | end
4 |
--------------------------------------------------------------------------------
/test/dummy/app/models/concerns/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/railsjazz/peity_vanilla_rails/726847a3b8cc0daa5215e8076331a4d1e18cd2de/test/dummy/app/models/concerns/.keep
--------------------------------------------------------------------------------
/test/dummy/app/views/home/index.html.erb:
--------------------------------------------------------------------------------
1 | Sparklines
2 |
3 | Line
4 | <%= peity_line_chart([115,123,234,-113,-43,-223,127,332,152,233]) %>
5 | <%= peity_line_chart([5,3,9,6,5,9,7,3,5,2,5,3,9,6,5,9,7,3,5,2], id: 'updating-chart') %>
6 |
7 |
19 |
20 | <%= peity_line_chart(100.times.map{rand(100) * [1,-1].sample}, options: { width: 240, fill: 'lightgreen', stroke: 'blue' }) %>
21 |
22 | Bar
23 | <%= peity_bar_chart([115,123,234,-113,-43,-223,127,332,152,233]) %>
24 |
25 | <%= peity_bar_chart('115,123,234,-132,152,233') %>
26 |
27 | <%= peity_bar_chart(50.times.map{rand(100) }, options: { width: 240, fill: ['orange'], height: 30, padding: -0.1 }) %>
28 |
29 | Pie
30 | <%= peity_pie_chart "2/3" %>
31 | <%= peity_pie_chart [3,10] %>
32 | <%= peity_pie_chart [3,10], options: { fill: ["red", "#eeeeee"], radius: 20 } %>
33 | <%= peity_pie_chart [236,300] %>
34 |
35 | Donut
36 | <%= peity_donut_chart "2/3" %>
37 | <%= peity_donut_chart [6,20] %>
38 | <%= peity_donut_chart [236,300] %>
39 |
--------------------------------------------------------------------------------
/test/dummy/app/views/layouts/application.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Dummy
5 |
6 | <%= csrf_meta_tags %>
7 | <%= csp_meta_tag %>
8 |
9 | <%= stylesheet_link_tag "application" %>
10 | <%= javascript_include_tag "application" %>
11 |
12 |
17 |
18 |
19 |
20 | <%= yield %>
21 |
22 |
23 |
--------------------------------------------------------------------------------
/test/dummy/app/views/layouts/mailer.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
8 |
9 |
10 |
11 | <%= yield %>
12 |
13 |
14 |
--------------------------------------------------------------------------------
/test/dummy/app/views/layouts/mailer.text.erb:
--------------------------------------------------------------------------------
1 | <%= yield %>
2 |
--------------------------------------------------------------------------------
/test/dummy/bin/rails:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | APP_PATH = File.expand_path("../config/application", __dir__)
3 | require_relative "../config/boot"
4 | require "rails/commands"
5 |
--------------------------------------------------------------------------------
/test/dummy/bin/rake:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | require_relative "../config/boot"
3 | require "rake"
4 | Rake.application.run
5 |
--------------------------------------------------------------------------------
/test/dummy/bin/setup:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | require "fileutils"
3 |
4 | # path to your application root.
5 | APP_ROOT = File.expand_path("..", __dir__)
6 |
7 | def system!(*args)
8 | system(*args) || abort("\n== Command #{args} failed ==")
9 | end
10 |
11 | FileUtils.chdir APP_ROOT do
12 | # This script is a way to set up or update your development environment automatically.
13 | # This script is idempotent, so that you can run it at any time and get an expectable outcome.
14 | # Add necessary setup steps to this file.
15 |
16 | puts "== Installing dependencies =="
17 | system! "gem install bundler --conservative"
18 | system("bundle check") || system!("bundle install")
19 |
20 | # puts "\n== Copying sample files =="
21 | # unless File.exist?("config/database.yml")
22 | # FileUtils.cp "config/database.yml.sample", "config/database.yml"
23 | # end
24 |
25 | puts "\n== Preparing database =="
26 | system! "bin/rails db:prepare"
27 |
28 | puts "\n== Removing old logs and tempfiles =="
29 | system! "bin/rails log:clear tmp:clear"
30 |
31 | puts "\n== Restarting application server =="
32 | system! "bin/rails restart"
33 | end
34 |
--------------------------------------------------------------------------------
/test/dummy/config.ru:
--------------------------------------------------------------------------------
1 | # This file is used by Rack-based servers to start the application.
2 |
3 | require_relative "config/environment"
4 |
5 | run Rails.application
6 | Rails.application.load_server
7 |
--------------------------------------------------------------------------------
/test/dummy/config/application.rb:
--------------------------------------------------------------------------------
1 | require_relative "boot"
2 |
3 | require "rails/all"
4 |
5 | # Require the gems listed in Gemfile, including any gems
6 | # you've limited to :test, :development, or :production.
7 | Bundler.require(*Rails.groups)
8 | require "peity_vanilla_rails"
9 |
10 | module Dummy
11 | class Application < Rails::Application
12 | config.load_defaults Rails::VERSION::STRING.to_f
13 |
14 | # Configuration for the application, engines, and railties goes here.
15 | #
16 | # These settings can be overridden in specific environments using the files
17 | # in config/environments, which are processed later.
18 | #
19 | # config.time_zone = "Central Time (US & Canada)"
20 | # config.eager_load_paths << Rails.root.join("extras")
21 | end
22 | end
23 |
--------------------------------------------------------------------------------
/test/dummy/config/boot.rb:
--------------------------------------------------------------------------------
1 | # Set up gems listed in the Gemfile.
2 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../../Gemfile", __dir__)
3 |
4 | require "bundler/setup" if File.exist?(ENV["BUNDLE_GEMFILE"])
5 | $LOAD_PATH.unshift File.expand_path("../../../lib", __dir__)
6 |
--------------------------------------------------------------------------------
/test/dummy/config/cable.yml:
--------------------------------------------------------------------------------
1 | development:
2 | adapter: async
3 |
4 | test:
5 | adapter: test
6 |
7 | production:
8 | adapter: redis
9 | url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %>
10 | channel_prefix: dummy_production
11 |
--------------------------------------------------------------------------------
/test/dummy/config/database.yml:
--------------------------------------------------------------------------------
1 | # SQLite. Versions 3.8.0 and up are supported.
2 | # gem install sqlite3
3 | #
4 | # Ensure the SQLite 3 gem is defined in your Gemfile
5 | # gem "sqlite3"
6 | #
7 | default: &default
8 | adapter: sqlite3
9 | pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
10 | timeout: 5000
11 |
12 | development:
13 | <<: *default
14 | database: db/development.sqlite3
15 |
16 | # Warning: The database defined as "test" will be erased and
17 | # re-generated from your development database when you run "rake".
18 | # Do not set this db to the same as development or production.
19 | test:
20 | <<: *default
21 | database: db/test.sqlite3
22 |
23 | production:
24 | <<: *default
25 | database: db/production.sqlite3
26 |
--------------------------------------------------------------------------------
/test/dummy/config/environment.rb:
--------------------------------------------------------------------------------
1 | # Load the Rails application.
2 | require_relative "application"
3 |
4 | # Initialize the Rails application.
5 | Rails.application.initialize!
6 |
--------------------------------------------------------------------------------
/test/dummy/config/environments/development.rb:
--------------------------------------------------------------------------------
1 | require "active_support/core_ext/integer/time"
2 |
3 | Rails.application.configure do
4 | # Settings specified here will take precedence over those in config/application.rb.
5 |
6 | # In the development environment your application's code is reloaded any time
7 | # it changes. This slows down response time but is perfect for development
8 | # since you don't have to restart the web server when you make code changes.
9 | config.cache_classes = false
10 |
11 | # Do not eager load code on boot.
12 | config.eager_load = false
13 |
14 | # Show full error reports.
15 | config.consider_all_requests_local = true
16 |
17 | # Enable server timing
18 | config.server_timing = true
19 |
20 | # Enable/disable caching. By default caching is disabled.
21 | # Run rails dev:cache to toggle caching.
22 | if Rails.root.join("tmp/caching-dev.txt").exist?
23 | config.action_controller.perform_caching = true
24 | config.action_controller.enable_fragment_cache_logging = true
25 |
26 | config.cache_store = :memory_store
27 | config.public_file_server.headers = {
28 | "Cache-Control" => "public, max-age=#{2.days.to_i}"
29 | }
30 | else
31 | config.action_controller.perform_caching = false
32 |
33 | config.cache_store = :null_store
34 | end
35 |
36 | # Store uploaded files on the local file system (see config/storage.yml for options).
37 | config.active_storage.service = :local
38 |
39 | # Don't care if the mailer can't send.
40 | config.action_mailer.raise_delivery_errors = false
41 |
42 | config.action_mailer.perform_caching = false
43 |
44 | # Print deprecation notices to the Rails logger.
45 | config.active_support.deprecation = :log
46 |
47 | # Raise exceptions for disallowed deprecations.
48 | config.active_support.disallowed_deprecation = :raise
49 |
50 | # Tell Active Support which deprecation messages to disallow.
51 | config.active_support.disallowed_deprecation_warnings = []
52 |
53 | # Raise an error on page load if there are pending migrations.
54 | config.active_record.migration_error = :page_load
55 |
56 | # Highlight code that triggered database queries in logs.
57 | config.active_record.verbose_query_logs = true
58 |
59 |
60 | # Raises error for missing translations.
61 | # config.i18n.raise_on_missing_translations = true
62 |
63 | # Annotate rendered view with file names.
64 | # config.action_view.annotate_rendered_view_with_filenames = true
65 |
66 | # Uncomment if you wish to allow Action Cable access from any origin.
67 | # config.action_cable.disable_request_forgery_protection = true
68 | end
69 |
--------------------------------------------------------------------------------
/test/dummy/config/environments/production.rb:
--------------------------------------------------------------------------------
1 | require "active_support/core_ext/integer/time"
2 |
3 | Rails.application.configure do
4 | # Settings specified here will take precedence over those in config/application.rb.
5 |
6 | # Code is not reloaded between requests.
7 | config.cache_classes = true
8 |
9 | # Eager load code on boot. This eager loads most of Rails and
10 | # your application in memory, allowing both threaded web servers
11 | # and those relying on copy on write to perform better.
12 | # Rake tasks automatically ignore this option for performance.
13 | config.eager_load = true
14 |
15 | # Full error reports are disabled and caching is turned on.
16 | config.consider_all_requests_local = false
17 | config.action_controller.perform_caching = true
18 |
19 | # Ensures that a master key has been made available in either ENV["RAILS_MASTER_KEY"]
20 | # or in config/master.key. This key is used to decrypt credentials (and other encrypted files).
21 | # config.require_master_key = true
22 |
23 | # Disable serving static files from the `/public` folder by default since
24 | # Apache or NGINX already handles this.
25 | config.public_file_server.enabled = ENV["RAILS_SERVE_STATIC_FILES"].present?
26 |
27 | # Enable serving of images, stylesheets, and JavaScripts from an asset server.
28 | # config.asset_host = "http://assets.example.com"
29 |
30 | # Specifies the header that your server uses for sending files.
31 | # config.action_dispatch.x_sendfile_header = "X-Sendfile" # for Apache
32 | # config.action_dispatch.x_sendfile_header = "X-Accel-Redirect" # for NGINX
33 |
34 | # Store uploaded files on the local file system (see config/storage.yml for options).
35 | config.active_storage.service = :local
36 |
37 | # Mount Action Cable outside main process or domain.
38 | # config.action_cable.mount_path = nil
39 | # config.action_cable.url = "wss://example.com/cable"
40 | # config.action_cable.allowed_request_origins = [ "http://example.com", /http:\/\/example.*/ ]
41 |
42 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
43 | # config.force_ssl = true
44 |
45 | # Include generic and useful information about system operation, but avoid logging too much
46 | # information to avoid inadvertent exposure of personally identifiable information (PII).
47 | config.log_level = :info
48 |
49 | # Prepend all log lines with the following tags.
50 | config.log_tags = [ :request_id ]
51 |
52 | # Use a different cache store in production.
53 | # config.cache_store = :mem_cache_store
54 |
55 | # Use a real queuing backend for Active Job (and separate queues per environment).
56 | # config.active_job.queue_adapter = :resque
57 | # config.active_job.queue_name_prefix = "dummy_production"
58 |
59 | config.action_mailer.perform_caching = false
60 |
61 | # Ignore bad email addresses and do not raise email delivery errors.
62 | # Set this to true and configure the email server for immediate delivery to raise delivery errors.
63 | # config.action_mailer.raise_delivery_errors = false
64 |
65 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to
66 | # the I18n.default_locale when a translation cannot be found).
67 | config.i18n.fallbacks = true
68 |
69 | # Don't log any deprecations.
70 | config.active_support.report_deprecations = false
71 |
72 | # Use default logging formatter so that PID and timestamp are not suppressed.
73 | config.log_formatter = ::Logger::Formatter.new
74 |
75 | # Use a different logger for distributed setups.
76 | # require "syslog/logger"
77 | # config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new "app-name")
78 |
79 | if ENV["RAILS_LOG_TO_STDOUT"].present?
80 | logger = ActiveSupport::Logger.new(STDOUT)
81 | logger.formatter = config.log_formatter
82 | config.logger = ActiveSupport::TaggedLogging.new(logger)
83 | end
84 |
85 | # Do not dump schema after migrations.
86 | config.active_record.dump_schema_after_migration = false
87 | end
88 |
--------------------------------------------------------------------------------
/test/dummy/config/environments/test.rb:
--------------------------------------------------------------------------------
1 | require "active_support/core_ext/integer/time"
2 |
3 | # The test environment is used exclusively to run your application's
4 | # test suite. You never need to work with it otherwise. Remember that
5 | # your test database is "scratch space" for the test suite and is wiped
6 | # and recreated between test runs. Don't rely on the data there!
7 |
8 | Rails.application.configure do
9 | # Settings specified here will take precedence over those in config/application.rb.
10 |
11 | # Turn false under Spring and add config.action_view.cache_template_loading = true.
12 | config.cache_classes = true
13 |
14 | # Eager loading loads your whole application. When running a single test locally,
15 | # this probably isn't necessary. It's a good idea to do in a continuous integration
16 | # system, or in some way before deploying your code.
17 | config.eager_load = ENV["CI"].present?
18 |
19 | # Configure public file server for tests with Cache-Control for performance.
20 | config.public_file_server.enabled = true
21 | config.public_file_server.headers = {
22 | "Cache-Control" => "public, max-age=#{1.hour.to_i}"
23 | }
24 |
25 | # Show full error reports and disable caching.
26 | config.consider_all_requests_local = true
27 | config.action_controller.perform_caching = false
28 | config.cache_store = :null_store
29 |
30 | # Raise exceptions instead of rendering exception templates.
31 | config.action_dispatch.show_exceptions = false
32 |
33 | # Disable request forgery protection in test environment.
34 | config.action_controller.allow_forgery_protection = false
35 |
36 | # Store uploaded files on the local file system in a temporary directory.
37 | config.active_storage.service = :test
38 |
39 | config.action_mailer.perform_caching = false
40 |
41 | # Tell Action Mailer not to deliver emails to the real world.
42 | # The :test delivery method accumulates sent emails in the
43 | # ActionMailer::Base.deliveries array.
44 | config.action_mailer.delivery_method = :test
45 |
46 | # Print deprecation notices to the stderr.
47 | config.active_support.deprecation = :stderr
48 |
49 | # Raise exceptions for disallowed deprecations.
50 | config.active_support.disallowed_deprecation = :raise
51 |
52 | # Tell Active Support which deprecation messages to disallow.
53 | config.active_support.disallowed_deprecation_warnings = []
54 |
55 | # Raises error for missing translations.
56 | # config.i18n.raise_on_missing_translations = true
57 |
58 | # Annotate rendered view with file names.
59 | # config.action_view.annotate_rendered_view_with_filenames = true
60 | end
61 |
--------------------------------------------------------------------------------
/test/dummy/config/initializers/content_security_policy.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Define an application-wide content security policy
4 | # For further information see the following documentation
5 | # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy
6 |
7 | # Rails.application.configure do
8 | # config.content_security_policy do |policy|
9 | # policy.default_src :self, :https
10 | # policy.font_src :self, :https, :data
11 | # policy.img_src :self, :https, :data
12 | # policy.object_src :none
13 | # policy.script_src :self, :https
14 | # policy.style_src :self, :https
15 | # # Specify URI for violation reports
16 | # # policy.report_uri "/csp-violation-report-endpoint"
17 | # end
18 | #
19 | # # Generate session nonces for permitted importmap and inline scripts
20 | # config.content_security_policy_nonce_generator = ->(request) { request.session.id.to_s }
21 | # config.content_security_policy_nonce_directives = %w(script-src)
22 | #
23 | # # Report CSP violations to a specified URI. See:
24 | # # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy-Report-Only
25 | # # config.content_security_policy_report_only = true
26 | # end
27 |
--------------------------------------------------------------------------------
/test/dummy/config/initializers/filter_parameter_logging.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Configure parameters to be filtered from the log file. Use this to limit dissemination of
4 | # sensitive information. See the ActiveSupport::ParameterFilter documentation for supported
5 | # notations and behaviors.
6 | Rails.application.config.filter_parameters += [
7 | :passw, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn
8 | ]
9 |
--------------------------------------------------------------------------------
/test/dummy/config/initializers/inflections.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Add new inflection rules using the following format. Inflections
4 | # are locale specific, and you may define rules for as many different
5 | # locales as you wish. All of these examples are active by default:
6 | # ActiveSupport::Inflector.inflections(:en) do |inflect|
7 | # inflect.plural /^(ox)$/i, "\\1en"
8 | # inflect.singular /^(ox)en/i, "\\1"
9 | # inflect.irregular "person", "people"
10 | # inflect.uncountable %w( fish sheep )
11 | # end
12 |
13 | # These inflection rules are supported but not enabled by default:
14 | # ActiveSupport::Inflector.inflections(:en) do |inflect|
15 | # inflect.acronym "RESTful"
16 | # end
17 |
--------------------------------------------------------------------------------
/test/dummy/config/initializers/permissions_policy.rb:
--------------------------------------------------------------------------------
1 | # Define an application-wide HTTP permissions policy. For further
2 | # information see https://developers.google.com/web/updates/2018/06/feature-policy
3 | #
4 | # Rails.application.config.permissions_policy do |f|
5 | # f.camera :none
6 | # f.gyroscope :none
7 | # f.microphone :none
8 | # f.usb :none
9 | # f.fullscreen :self
10 | # f.payment :self, "https://secure.example.com"
11 | # end
12 |
--------------------------------------------------------------------------------
/test/dummy/config/locales/en.yml:
--------------------------------------------------------------------------------
1 | # Files in the config/locales directory are used for internationalization
2 | # and are automatically loaded by Rails. If you want to use locales other
3 | # than English, add the necessary files in this directory.
4 | #
5 | # To use the locales, use `I18n.t`:
6 | #
7 | # I18n.t "hello"
8 | #
9 | # In views, this is aliased to just `t`:
10 | #
11 | # <%= t("hello") %>
12 | #
13 | # To use a different locale, set it with `I18n.locale`:
14 | #
15 | # I18n.locale = :es
16 | #
17 | # This would use the information in config/locales/es.yml.
18 | #
19 | # The following keys must be escaped otherwise they will not be retrieved by
20 | # the default I18n backend:
21 | #
22 | # true, false, on, off, yes, no
23 | #
24 | # Instead, surround them with single quotes.
25 | #
26 | # en:
27 | # "true": "foo"
28 | #
29 | # To learn more, please read the Rails Internationalization guide
30 | # available at https://guides.rubyonrails.org/i18n.html.
31 |
32 | en:
33 | hello: "Hello world"
34 |
--------------------------------------------------------------------------------
/test/dummy/config/puma.rb:
--------------------------------------------------------------------------------
1 | # Puma can serve each request in a thread from an internal thread pool.
2 | # The `threads` method setting takes two numbers: a minimum and maximum.
3 | # Any libraries that use thread pools should be configured to match
4 | # the maximum value specified for Puma. Default is set to 5 threads for minimum
5 | # and maximum; this matches the default thread size of Active Record.
6 | #
7 | max_threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }
8 | min_threads_count = ENV.fetch("RAILS_MIN_THREADS") { max_threads_count }
9 | threads min_threads_count, max_threads_count
10 |
11 | # Specifies the `worker_timeout` threshold that Puma will use to wait before
12 | # terminating a worker in development environments.
13 | #
14 | worker_timeout 3600 if ENV.fetch("RAILS_ENV", "development") == "development"
15 |
16 | # Specifies the `port` that Puma will listen on to receive requests; default is 3000.
17 | #
18 | port ENV.fetch("PORT") { 3000 }
19 |
20 | # Specifies the `environment` that Puma will run in.
21 | #
22 | environment ENV.fetch("RAILS_ENV") { "development" }
23 |
24 | # Specifies the `pidfile` that Puma will use.
25 | pidfile ENV.fetch("PIDFILE") { "tmp/pids/server.pid" }
26 |
27 | # Specifies the number of `workers` to boot in clustered mode.
28 | # Workers are forked web server processes. If using threads and workers together
29 | # the concurrency of the application would be max `threads` * `workers`.
30 | # Workers do not work on JRuby or Windows (both of which do not support
31 | # processes).
32 | #
33 | # workers ENV.fetch("WEB_CONCURRENCY") { 2 }
34 |
35 | # Use the `preload_app!` method when specifying a `workers` number.
36 | # This directive tells Puma to first boot the application and load code
37 | # before forking the application. This takes advantage of Copy On Write
38 | # process behavior so workers use less memory.
39 | #
40 | # preload_app!
41 |
42 | # Allow puma to be restarted by `bin/rails restart` command.
43 | plugin :tmp_restart
44 |
--------------------------------------------------------------------------------
/test/dummy/config/routes.rb:
--------------------------------------------------------------------------------
1 | Rails.application.routes.draw do
2 | root to: "home#index"
3 | end
4 |
--------------------------------------------------------------------------------
/test/dummy/config/storage.yml:
--------------------------------------------------------------------------------
1 | test:
2 | service: Disk
3 | root: <%= Rails.root.join("tmp/storage") %>
4 |
5 | local:
6 | service: Disk
7 | root: <%= Rails.root.join("storage") %>
8 |
9 | # Use bin/rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key)
10 | # amazon:
11 | # service: S3
12 | # access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %>
13 | # secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %>
14 | # region: us-east-1
15 | # bucket: your_own_bucket-<%= Rails.env %>
16 |
17 | # Remember not to checkin your GCS keyfile to a repository
18 | # google:
19 | # service: GCS
20 | # project: your_project
21 | # credentials: <%= Rails.root.join("path/to/gcs.keyfile") %>
22 | # bucket: your_own_bucket-<%= Rails.env %>
23 |
24 | # Use bin/rails credentials:edit to set the Azure Storage secret (as azure_storage:storage_access_key)
25 | # microsoft:
26 | # service: AzureStorage
27 | # storage_account_name: your_account_name
28 | # storage_access_key: <%= Rails.application.credentials.dig(:azure_storage, :storage_access_key) %>
29 | # container: your_container_name-<%= Rails.env %>
30 |
31 | # mirror:
32 | # service: Mirror
33 | # primary: local
34 | # mirrors: [ amazon, google, microsoft ]
35 |
--------------------------------------------------------------------------------
/test/dummy/lib/assets/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/railsjazz/peity_vanilla_rails/726847a3b8cc0daa5215e8076331a4d1e18cd2de/test/dummy/lib/assets/.keep
--------------------------------------------------------------------------------
/test/dummy/log/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/railsjazz/peity_vanilla_rails/726847a3b8cc0daa5215e8076331a4d1e18cd2de/test/dummy/log/.keep
--------------------------------------------------------------------------------
/test/dummy/node_modules/.yarn-integrity:
--------------------------------------------------------------------------------
1 | {
2 | "systemParams": "darwin-x64-102",
3 | "modulesFolders": [],
4 | "flags": [],
5 | "linkedModules": [],
6 | "topLevelPatterns": [],
7 | "lockfileEntries": {},
8 | "files": [],
9 | "artifacts": {}
10 | }
--------------------------------------------------------------------------------
/test/dummy/public/404.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | The page you were looking for doesn't exist (404)
5 |
6 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
The page you were looking for doesn't exist.
62 |
You may have mistyped the address or the page may have moved.
63 |
64 |
If you are the application owner check the logs for more information.
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/test/dummy/public/422.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | The change you wanted was rejected (422)
5 |
6 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
The change you wanted was rejected.
62 |
Maybe you tried to change something you didn't have access to.
63 |
64 |
If you are the application owner check the logs for more information.
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/test/dummy/public/500.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | We're sorry, but something went wrong (500)
5 |
6 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
We're sorry, but something went wrong.
62 |
63 |
If you are the application owner check the logs for more information.
64 |
65 |
66 |
67 |
--------------------------------------------------------------------------------
/test/dummy/public/apple-touch-icon-precomposed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/railsjazz/peity_vanilla_rails/726847a3b8cc0daa5215e8076331a4d1e18cd2de/test/dummy/public/apple-touch-icon-precomposed.png
--------------------------------------------------------------------------------
/test/dummy/public/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/railsjazz/peity_vanilla_rails/726847a3b8cc0daa5215e8076331a4d1e18cd2de/test/dummy/public/apple-touch-icon.png
--------------------------------------------------------------------------------
/test/dummy/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/railsjazz/peity_vanilla_rails/726847a3b8cc0daa5215e8076331a4d1e18cd2de/test/dummy/public/favicon.ico
--------------------------------------------------------------------------------
/test/dummy/test/controllers/home_controller_test.rb:
--------------------------------------------------------------------------------
1 | require "test_helper"
2 |
3 | class HomeControllerTest < ActionDispatch::IntegrationTest
4 | test "should get index" do
5 | get home_index_url
6 | assert_response :success
7 | end
8 | end
9 |
--------------------------------------------------------------------------------
/test/dummy/yarn.lock:
--------------------------------------------------------------------------------
1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
2 | # yarn lockfile v1
3 |
4 |
5 |
--------------------------------------------------------------------------------
/test/peity_vanilla_rails_test.rb:
--------------------------------------------------------------------------------
1 | require "test_helper"
2 |
3 | class PeityVanillaRailsTest < ActiveSupport::TestCase
4 | test "it has a version number" do
5 | assert PeityVanillaRails::VERSION
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/test/test_helper.rb:
--------------------------------------------------------------------------------
1 | # Configure Rails Environment
2 | ENV["RAILS_ENV"] = "test"
3 |
4 | require_relative "../test/dummy/config/environment"
5 | ActiveRecord::Migrator.migrations_paths = [File.expand_path("../test/dummy/db/migrate", __dir__)]
6 | require "rails/test_help"
7 |
8 | # Load fixtures from the engine
9 | if ActiveSupport::TestCase.respond_to?(:fixture_path=)
10 | ActiveSupport::TestCase.fixture_path = File.expand_path("fixtures", __dir__)
11 | ActionDispatch::IntegrationTest.fixture_path = ActiveSupport::TestCase.fixture_path
12 | ActiveSupport::TestCase.file_fixture_path = ActiveSupport::TestCase.fixture_path + "/files"
13 | ActiveSupport::TestCase.fixtures :all
14 | end
15 |
--------------------------------------------------------------------------------