plasticine 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +9 -0
  3. data/.travis.yml +5 -0
  4. data/Gemfile +4 -0
  5. data/LICENSE +8 -0
  6. data/README.md +18 -0
  7. data/Rakefile +11 -0
  8. data/app/assets/javascripts/plasticine/base.coffee +13 -0
  9. data/app/assets/javascripts/plasticine/column.coffee +243 -0
  10. data/app/assets/javascripts/plasticine/index.js +3 -0
  11. data/app/assets/stylesheets/plasticine/base.scss +1 -0
  12. data/app/assets/stylesheets/plasticine/column.scss +44 -0
  13. data/app/assets/stylesheets/plasticine/index.css +4 -0
  14. data/app/controllers/plasticine_controller.rb +53 -0
  15. data/config/locales/en.yml +26 -0
  16. data/config/locales/fr.yml +26 -0
  17. data/config/routes.rb +3 -0
  18. data/lib/plasticine.rb +23 -0
  19. data/lib/plasticine/authentication.rb +43 -0
  20. data/lib/plasticine/base_visual.rb +11 -0
  21. data/lib/plasticine/builder.rb +43 -0
  22. data/lib/plasticine/builder/base.rb +58 -0
  23. data/lib/plasticine/builder/column.rb +41 -0
  24. data/lib/plasticine/engine.rb +7 -0
  25. data/lib/plasticine/helpers.rb +81 -0
  26. data/lib/plasticine/railtie.rb +15 -0
  27. data/plasticine.gemspec +22 -0
  28. data/spec/controllers/application_controller_spec.rb +18 -0
  29. data/spec/dummy/Rakefile +6 -0
  30. data/spec/dummy/app/controllers/application_controller.rb +3 -0
  31. data/spec/dummy/app/controllers/examples_controller.rb +11 -0
  32. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  33. data/spec/dummy/app/models/anonymou.rb +2 -0
  34. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  35. data/spec/dummy/bin/bundle +3 -0
  36. data/spec/dummy/bin/rails +4 -0
  37. data/spec/dummy/bin/rake +4 -0
  38. data/spec/dummy/config.ru +4 -0
  39. data/spec/dummy/config/application.rb +18 -0
  40. data/spec/dummy/config/boot.rb +5 -0
  41. data/spec/dummy/config/database.yml +5 -0
  42. data/spec/dummy/config/environment.rb +5 -0
  43. data/spec/dummy/config/environments/development.rb +29 -0
  44. data/spec/dummy/config/environments/production.rb +80 -0
  45. data/spec/dummy/config/environments/test.rb +36 -0
  46. data/spec/dummy/config/initializers/standard_rails_initializers.rb +9 -0
  47. data/spec/dummy/config/routes.rb +3 -0
  48. data/spec/dummy/db/test.sqlite3 +0 -0
  49. data/spec/plasticine/base_spec.rb +7 -0
  50. data/spec/plasticine_spec.rb +4 -0
  51. data/spec/spec_helper.rb +25 -0
  52. data/spec/support/views_helper.rb +12 -0
  53. data/vendor/assets/javascripts/d3.js +6 -0
  54. 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
@@ -0,0 +1,3 @@
1
+ Rails.application.routes.draw do
2
+ resources :plasticine, only: [:index, :show], controller: "plasticine"
3
+ end
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,11 @@
1
+ module Plasticine
2
+ class BaseVisual
3
+ def initialize(params)
4
+ @params = params
5
+
6
+ @from = params[:from] ? Time.parse(params[:from]) : nil
7
+ @to = params[:to] ? Time.parse(params[:to]) : Time.now
8
+ @step = params[:step] || 'month'
9
+ end
10
+ end
11
+ 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
+
@@ -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
@@ -0,0 +1,6 @@
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 File.expand_path('../config/application', __FILE__)
5
+
6
+ Dummy::Application.load_tasks