plasticine 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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