plasticine 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/.travis.yml +5 -0
- data/Gemfile +4 -0
- data/LICENSE +8 -0
- data/README.md +18 -0
- data/Rakefile +11 -0
- data/app/assets/javascripts/plasticine/base.coffee +13 -0
- data/app/assets/javascripts/plasticine/column.coffee +243 -0
- data/app/assets/javascripts/plasticine/index.js +3 -0
- data/app/assets/stylesheets/plasticine/base.scss +1 -0
- data/app/assets/stylesheets/plasticine/column.scss +44 -0
- data/app/assets/stylesheets/plasticine/index.css +4 -0
- data/app/controllers/plasticine_controller.rb +53 -0
- data/config/locales/en.yml +26 -0
- data/config/locales/fr.yml +26 -0
- data/config/routes.rb +3 -0
- data/lib/plasticine.rb +23 -0
- data/lib/plasticine/authentication.rb +43 -0
- data/lib/plasticine/base_visual.rb +11 -0
- data/lib/plasticine/builder.rb +43 -0
- data/lib/plasticine/builder/base.rb +58 -0
- data/lib/plasticine/builder/column.rb +41 -0
- data/lib/plasticine/engine.rb +7 -0
- data/lib/plasticine/helpers.rb +81 -0
- data/lib/plasticine/railtie.rb +15 -0
- data/plasticine.gemspec +22 -0
- data/spec/controllers/application_controller_spec.rb +18 -0
- data/spec/dummy/Rakefile +6 -0
- data/spec/dummy/app/controllers/application_controller.rb +3 -0
- data/spec/dummy/app/controllers/examples_controller.rb +11 -0
- data/spec/dummy/app/helpers/application_helper.rb +2 -0
- data/spec/dummy/app/models/anonymou.rb +2 -0
- data/spec/dummy/app/views/layouts/application.html.erb +14 -0
- data/spec/dummy/bin/bundle +3 -0
- data/spec/dummy/bin/rails +4 -0
- data/spec/dummy/bin/rake +4 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/config/application.rb +18 -0
- data/spec/dummy/config/boot.rb +5 -0
- data/spec/dummy/config/database.yml +5 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +29 -0
- data/spec/dummy/config/environments/production.rb +80 -0
- data/spec/dummy/config/environments/test.rb +36 -0
- data/spec/dummy/config/initializers/standard_rails_initializers.rb +9 -0
- data/spec/dummy/config/routes.rb +3 -0
- data/spec/dummy/db/test.sqlite3 +0 -0
- data/spec/plasticine/base_spec.rb +7 -0
- data/spec/plasticine_spec.rb +4 -0
- data/spec/spec_helper.rb +25 -0
- data/spec/support/views_helper.rb +12 -0
- data/vendor/assets/javascripts/d3.js +6 -0
- metadata +191 -0
@@ -0,0 +1,26 @@
|
|
1
|
+
en:
|
2
|
+
date:
|
3
|
+
formats:
|
4
|
+
plasticine_date_without_day: "%B %Y"
|
5
|
+
plasticine_date_long: "%e %B %Y"
|
6
|
+
plasticine_date_long_without_year: "%e %B"
|
7
|
+
plasticine_day: "%e"
|
8
|
+
plasticine_year: "%Y"
|
9
|
+
|
10
|
+
plasticine:
|
11
|
+
base:
|
12
|
+
steps:
|
13
|
+
day: Day
|
14
|
+
week: Week
|
15
|
+
month: Month
|
16
|
+
quarter: Quarter
|
17
|
+
year: Year
|
18
|
+
|
19
|
+
time:
|
20
|
+
formats:
|
21
|
+
plasticine_date_without_day: "%B %Y"
|
22
|
+
plasticine_date_long: "%B %e, %Y"
|
23
|
+
plasticine_date_long_without_year: "%e %B"
|
24
|
+
plasticine_day: "%e"
|
25
|
+
plasticine_long: "%B %d, %Y %H:%M"
|
26
|
+
plasticine_year: "%Y"
|
@@ -0,0 +1,26 @@
|
|
1
|
+
fr:
|
2
|
+
date:
|
3
|
+
formats:
|
4
|
+
plasticine_date_without_day: "%B %Y"
|
5
|
+
plasticine_date_long: "%e %B %Y"
|
6
|
+
plasticine_date_long_without_year: "%e %B"
|
7
|
+
plasticine_day: "%e"
|
8
|
+
plasticine_year: "%Y"
|
9
|
+
|
10
|
+
plasticine:
|
11
|
+
base:
|
12
|
+
steps:
|
13
|
+
day: Jour
|
14
|
+
week: Semaine
|
15
|
+
month: Mois
|
16
|
+
quarter: Trimestre
|
17
|
+
year: Année
|
18
|
+
|
19
|
+
time:
|
20
|
+
formats:
|
21
|
+
plasticine_date_without_day: "%B %Y"
|
22
|
+
plasticine_date_long: "%e %B %Y"
|
23
|
+
plasticine_date_long_without_year: "%e %B"
|
24
|
+
plasticine_day: "%e"
|
25
|
+
plasticine_long: "%e %B %Y à %H:%M"
|
26
|
+
plasticine_year: "%Y"
|
data/config/routes.rb
ADDED
data/lib/plasticine.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
module Plasticine
|
2
|
+
def self.localize(path, options={})
|
3
|
+
options.reverse_merge! format: :date_long
|
4
|
+
|
5
|
+
options[:format] = "plasticine_#{options[:format]}".to_sym
|
6
|
+
|
7
|
+
return I18n.localize(path, options)
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.translate(path, options={})
|
11
|
+
I18n.translate("plasticine.#{path}", options)
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.t(path, options={})
|
15
|
+
self.translate path, options
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
require "plasticine/authentication"
|
20
|
+
require "plasticine/base_visual"
|
21
|
+
require "plasticine/builder"
|
22
|
+
require "plasticine/engine"
|
23
|
+
require "plasticine/railtie"
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
|
3
|
+
module Plasticine
|
4
|
+
class Authentication
|
5
|
+
|
6
|
+
def initialize(request_url, params={})
|
7
|
+
@request_url = request_url
|
8
|
+
@params = params
|
9
|
+
end
|
10
|
+
|
11
|
+
def expired?
|
12
|
+
@params[:timestamp] and Time.parse(@params[:timestamp]) + 12.hours < Time.now
|
13
|
+
end
|
14
|
+
|
15
|
+
def valid_token?
|
16
|
+
@params[:token] == tokenize
|
17
|
+
end
|
18
|
+
|
19
|
+
def tokenize
|
20
|
+
OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA1.new, token_key, filtered_url)
|
21
|
+
end
|
22
|
+
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def compacted_params
|
27
|
+
@params.map{ |k,v| "#{k}#{v}" if not reserved_params.include?(k.to_s) }.join
|
28
|
+
end
|
29
|
+
|
30
|
+
def filtered_url
|
31
|
+
url = @request_url.split('?').first.rpartition('/').first + compacted_params
|
32
|
+
url.chars.sort.join.gsub('/', '')
|
33
|
+
end
|
34
|
+
|
35
|
+
def reserved_params
|
36
|
+
['action', 'class', 'controller', 'format', 'from', 'nature', 'step', 'to', 'token', 'tools', 'update_every', 'version']
|
37
|
+
end
|
38
|
+
|
39
|
+
def token_key
|
40
|
+
Rails.application.config.secret_key_base
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Plasticine::Builder
|
2
|
+
def self.period(start_at, end_at, options={})
|
3
|
+
options.reverse_merge!(step: 'day')
|
4
|
+
|
5
|
+
start_at_format = case
|
6
|
+
when options[:step] == 'month' then :date_without_day
|
7
|
+
when options[:step] == 'year' then :year
|
8
|
+
when start_at.year != end_at.year then :date_long
|
9
|
+
when start_at.month != end_at.month then :date_long_without_year
|
10
|
+
else :day
|
11
|
+
end
|
12
|
+
|
13
|
+
end_at_format = case options[:step]
|
14
|
+
when 'month' then :date_without_day
|
15
|
+
when 'year' then :year
|
16
|
+
else :date_long
|
17
|
+
end
|
18
|
+
|
19
|
+
content = []
|
20
|
+
content << Plasticine.localize(start_at, format: start_at_format)
|
21
|
+
content << (['month', 'year'].include?(options[:step]) ? Plasticine.t('date.period.to_month') : Plasticine.t('date.period.to'))
|
22
|
+
content << Plasticine.localize(end_at, format: end_at_format)
|
23
|
+
|
24
|
+
content[0].capitalize!
|
25
|
+
|
26
|
+
return content.join(' ')
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.get_step_from_interval(interval)
|
30
|
+
days = (interval / (3600 * 24)).to_i.abs
|
31
|
+
|
32
|
+
return case days
|
33
|
+
when 0..5 then 'day'
|
34
|
+
when 6..27 then 'week'
|
35
|
+
when 28..88 then 'month'
|
36
|
+
when 89..363 then 'quarter'
|
37
|
+
else 'year'
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
require 'plasticine/builder/base'
|
43
|
+
require 'plasticine/builder/column'
|
@@ -0,0 +1,58 @@
|
|
1
|
+
class Plasticine::Builder::Base
|
2
|
+
attr_accessor :visual
|
3
|
+
|
4
|
+
def initialize(id, options={})
|
5
|
+
options.reverse_merge! from: nil, to: nil, step: nil
|
6
|
+
|
7
|
+
@from = options[:from] ? Time.parse(options[:from]) : nil
|
8
|
+
@to = options[:to] ? Time.parse(options[:to]) : Time.now
|
9
|
+
|
10
|
+
@visual = { id: id }
|
11
|
+
@visual[:step] = options[:step] if options[:step]
|
12
|
+
@visual[:title] = t('title')
|
13
|
+
@visual[:period] = Plasticine::Builder.period(@from, @to, step: @visual[:step]) if @from
|
14
|
+
end
|
15
|
+
|
16
|
+
def close_visual
|
17
|
+
# Hooks before closing a visual
|
18
|
+
end
|
19
|
+
|
20
|
+
def id
|
21
|
+
@visual[:id]
|
22
|
+
end
|
23
|
+
|
24
|
+
def l(date, options={})
|
25
|
+
options.reverse_merge!(format: :date_long)
|
26
|
+
|
27
|
+
Plasticine.localize(date, options)
|
28
|
+
end
|
29
|
+
|
30
|
+
def period
|
31
|
+
@visual[:period]
|
32
|
+
end
|
33
|
+
|
34
|
+
def set_period_from_data(data)
|
35
|
+
@from = data.first
|
36
|
+
@to = data.last
|
37
|
+
|
38
|
+
@visual[:step] = Plasticine::Builder.get_step_from_interval(data[1] - data[0]) if not @visual[:step]
|
39
|
+
|
40
|
+
@visual[:period] = Plasticine::Builder.period @from, @to, step: @visual[:step]
|
41
|
+
end
|
42
|
+
|
43
|
+
def t(path, vars={})
|
44
|
+
vars.reverse_merge! default: ''
|
45
|
+
|
46
|
+
Plasticine.translate("#{id.gsub('-', '_')}.#{path}", vars)
|
47
|
+
end
|
48
|
+
|
49
|
+
def title
|
50
|
+
@visual[:title]
|
51
|
+
end
|
52
|
+
|
53
|
+
def to_json
|
54
|
+
close_visual
|
55
|
+
|
56
|
+
@visual.to_json
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
class Plasticine::Builder::Column < Plasticine::Builder::Base
|
2
|
+
def initialize(id, options={})
|
3
|
+
super
|
4
|
+
|
5
|
+
@visual.merge! columns: [], nature: 'column', axis_x_format: :string, axis_y_format: :number, axis_y_tick_count: 10, quarter_start_month: 1
|
6
|
+
@columns = {}
|
7
|
+
end
|
8
|
+
|
9
|
+
def add_column(x, y, options={})
|
10
|
+
options.reverse_merge! tooltip: nil
|
11
|
+
|
12
|
+
@columns[x] = [] if not @columns[x]
|
13
|
+
|
14
|
+
@columns[x] << { tooltip: options[:tooltip], y: y }
|
15
|
+
end
|
16
|
+
|
17
|
+
def axis_x_format=(format)
|
18
|
+
@visual[:axis_x_format] = format
|
19
|
+
end
|
20
|
+
|
21
|
+
def axis_y_format=(format)
|
22
|
+
@visual[:axis_y_format] = format
|
23
|
+
end
|
24
|
+
|
25
|
+
def axis_y_tick_count=(tick_count)
|
26
|
+
@visual[:axis_y_tick_count] = tick_count
|
27
|
+
end
|
28
|
+
|
29
|
+
def quarter_start_month=(month)
|
30
|
+
@visual[:quarter_start_month] = month
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
def close_visual
|
35
|
+
super
|
36
|
+
|
37
|
+
@columns.each do |x, data|
|
38
|
+
@visual[:columns] << { x_value: x, y_values: data.map{ |d| d[:y] }, tooltip: data[0][:tooltip] }
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,7 @@
|
|
1
|
+
module Plasticine
|
2
|
+
#*************************************************************************************
|
3
|
+
# Make the gem behave as an engine.
|
4
|
+
#*************************************************************************************
|
5
|
+
class Engine < Rails::Engine
|
6
|
+
end
|
7
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
module Plasticine::Helpers
|
2
|
+
def d3_include_tag
|
3
|
+
('<![if ! lt IE 9]>' + javascript_include_tag("d3") + '<![endif]>').html_safe
|
4
|
+
end
|
5
|
+
|
6
|
+
def column_visual(id, options={})
|
7
|
+
PlasticineTagHelper.new(self, id, options[:from], options[:to], options[:step]).column(options)
|
8
|
+
end
|
9
|
+
|
10
|
+
|
11
|
+
class PlasticineTagHelper
|
12
|
+
attr_reader :rails_helpers
|
13
|
+
|
14
|
+
delegate :content_tag, to: :rails_helpers
|
15
|
+
delegate :link_to, to: :rails_helpers
|
16
|
+
delegate :plasticine_url, to: :rails_helpers
|
17
|
+
|
18
|
+
def initialize(rails_helpers, id, from, to, step)
|
19
|
+
@rails_helpers = rails_helpers
|
20
|
+
@id = id
|
21
|
+
@from = from
|
22
|
+
@to = to
|
23
|
+
@step = step || 'month'
|
24
|
+
end
|
25
|
+
|
26
|
+
def column(options={})
|
27
|
+
options.reverse_merge! columns_left_padding: 20, columns_right_padding: 20, columns_margin: 0.2, y_spacing_ratio: 1.10
|
28
|
+
|
29
|
+
options[:data] = {
|
30
|
+
columns_left_padding: options.delete(:columns_left_padding),
|
31
|
+
columns_right_padding: options.delete(:columns_right_padding),
|
32
|
+
columns_margin: options.delete(:columns_margin),
|
33
|
+
y_spacing_ratio: options.delete(:y_spacing_ratio)
|
34
|
+
}
|
35
|
+
|
36
|
+
visual 'column', options
|
37
|
+
end
|
38
|
+
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def build_url(nature, options={})
|
43
|
+
params = { from: @from, format: 'json', id: @id, nature: nature, nonce: SecureRandom.hex(20), step: @step, timestamp: Time.now.utc.iso8601, to: @to, version: 'v1' }
|
44
|
+
|
45
|
+
options.each { |k,v| params[k] = v if not ['class', 'data', 'tools'].include? k.to_s }
|
46
|
+
|
47
|
+
params[:token] = build_url_token(params)
|
48
|
+
|
49
|
+
@url = plasticine_url(params)
|
50
|
+
end
|
51
|
+
|
52
|
+
def build_url_token(params)
|
53
|
+
base_url = plasticine_url(id: params[:id])
|
54
|
+
authentificator = Plasticine::Authentication.new(base_url, params)
|
55
|
+
authentificator.tokenize
|
56
|
+
end
|
57
|
+
|
58
|
+
def t(path)
|
59
|
+
Plasticine.translate(path)
|
60
|
+
end
|
61
|
+
|
62
|
+
def visual(nature, options={})
|
63
|
+
options.reverse_merge! class: "", data: {}
|
64
|
+
|
65
|
+
options[:class] += " plasticine plasticine-#{nature}"
|
66
|
+
|
67
|
+
build_url nature, options
|
68
|
+
|
69
|
+
options[:data][:visual] = nature
|
70
|
+
options[:data][:url] = @url
|
71
|
+
options[:data][:update_delay] = options[:update_every].to_i if options[:update_every]
|
72
|
+
|
73
|
+
content = content_tag(:div, '', id: @id, class: options[:class], data: options[:data])
|
74
|
+
content << link_to(@url, @url)
|
75
|
+
|
76
|
+
content
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
::ActionView::Base.send :include, self
|
81
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Plasticine
|
2
|
+
class Railtie < Rails::Railtie
|
3
|
+
initializer "plasticine" do |app|
|
4
|
+
ActiveSupport.on_load :action_view do
|
5
|
+
require 'plasticine/helpers'
|
6
|
+
end
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
dir = File.expand_path(File.dirname(__FILE__))
|
12
|
+
languages = [:fr, :en]
|
13
|
+
|
14
|
+
I18n.load_path += languages.map { |l| File.join(dir, '../../config/locales', "#{l}.yml") }
|
15
|
+
|
data/plasticine.gemspec
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Gem::Specification.new do |gem|
|
2
|
+
gem.name = "plasticine"
|
3
|
+
gem.description = "Data visualization toolkit for Rails App using D3.js"
|
4
|
+
gem.summary = "Data visualization toolkit for Rails App using D3.js"
|
5
|
+
gem.homepage = "https://github.com/alchimikweb/plasticine"
|
6
|
+
gem.version = "1.1.0"
|
7
|
+
gem.licenses = ["MIT"]
|
8
|
+
|
9
|
+
gem.authors = ["Sebastien Rosa"]
|
10
|
+
gem.email = ["sebastien@alchimik.com"]
|
11
|
+
|
12
|
+
gem.files = `git ls-files`.split($\)
|
13
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
14
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
15
|
+
gem.require_paths = ["lib"]
|
16
|
+
|
17
|
+
gem.add_dependency "rails", ['~> 5.0']
|
18
|
+
gem.add_development_dependency "sqlite3", "~> 1.3"
|
19
|
+
gem.add_development_dependency "rspec-rails", "~> 3.7"
|
20
|
+
gem.add_development_dependency "simplecov", "~> 0.15"
|
21
|
+
gem.add_development_dependency "simplecov-rcov-text", "~> 0"
|
22
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe ApplicationController, type: :controller do
|
4
|
+
controller do
|
5
|
+
def index
|
6
|
+
render plain: 'Done'
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
describe "when calling index action" do
|
11
|
+
it 'should be done' do
|
12
|
+
get :index
|
13
|
+
|
14
|
+
expect(response.status).to eq(200)
|
15
|
+
expect(response.body).to eq "Done"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
data/spec/dummy/Rakefile
ADDED