chart-candy 0.0.7
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +4 -0
- data/LICENSE +7 -0
- data/README.md +109 -0
- data/Rakefile +11 -0
- data/app/assets/javascripts/chart_candy/base.coffee +16 -0
- data/app/assets/javascripts/chart_candy/counter.coffee +50 -0
- data/app/assets/javascripts/chart_candy/donut.coffee +162 -0
- data/app/assets/javascripts/chart_candy/index.js +4 -0
- data/app/assets/javascripts/chart_candy/line.coffee +338 -0
- data/app/assets/stylesheets/chart_candy/base.css.scss +66 -0
- data/app/assets/stylesheets/chart_candy/counter.css.scss +11 -0
- data/app/assets/stylesheets/chart_candy/donut.css.scss +34 -0
- data/app/assets/stylesheets/chart_candy/index.css +6 -0
- data/app/assets/stylesheets/chart_candy/line.css.scss +107 -0
- data/app/controllers/candy_charts_controller.rb +60 -0
- data/config/locales/fr.yml +36 -0
- data/config/routes.rb +3 -0
- data/lib/chart-candy.rb +23 -0
- data/lib/chart-candy/authentication.rb +45 -0
- data/lib/chart-candy/base_chart.rb +11 -0
- data/lib/chart-candy/builder.rb +46 -0
- data/lib/chart-candy/builder/base.rb +70 -0
- data/lib/chart-candy/builder/counter.rb +12 -0
- data/lib/chart-candy/builder/donut.rb +73 -0
- data/lib/chart-candy/builder/line.rb +114 -0
- data/lib/chart-candy/builder/xls_builder.rb +159 -0
- data/lib/chart-candy/engine.rb +5 -0
- data/lib/chart-candy/helpers.rb +169 -0
- data/lib/chart-candy/implants.rb +10 -0
- data/lib/chart-candy/implants/railtie.rb +17 -0
- data/spec/chart-candy_spec.rb +11 -0
- data/spec/spec_helper.rb +12 -0
- data/vendor/assets/javascripts/d3.js +4 -0
- metadata +118 -0
@@ -0,0 +1,66 @@
|
|
1
|
+
div.wrapper-chart{font-family:"Lucida Grande", Arial;float:left;margin:0px 0px 40px 0px;width:100%;
|
2
|
+
h2{clear:both;float:left;font-size:24px;margin:0px 0px 20px 0px;width:100%;}
|
3
|
+
h2 span.subtitle{clear:both;color:#777;display:block;font-size:16px;}
|
4
|
+
|
5
|
+
div.tools{border:1px solid #dadada;border-bottom:1px solid #ccc;border-top-left-radius:8px;border-top-right-radius:8px;clear:both;float:left;height:50px;margin:0px 0px -1px 0px;padding:0px;width:100%;
|
6
|
+
background: #ececec;
|
7
|
+
background: linear-gradient(top, #F2F2F2, #ececec);
|
8
|
+
background: -moz-linear-gradient(top, #F2F2F2, #ececec);
|
9
|
+
background: -webkit-linear-gradient(top, #F2F2F2, #ececec);
|
10
|
+
|
11
|
+
select{margin:5px 0px 0px 0px;}
|
12
|
+
|
13
|
+
div.tool{float:left;margin:10px 30px 0px 0px;}
|
14
|
+
|
15
|
+
div.holder-template{margin-left:30px;
|
16
|
+
ul.items{margin:0px;
|
17
|
+
li{padding:0px;}
|
18
|
+
li span.text{background-image:image-url("application/icon-charts.png");display:block;text-indent:-9999px;height:15px;margin:0px 15px;padding-left:0px;padding-right:0px;width:20px;}
|
19
|
+
li.table span.text{background-position:-100px 5px;}
|
20
|
+
li.selected.table span.text{background-position:-100px -45px;}
|
21
|
+
}
|
22
|
+
}
|
23
|
+
|
24
|
+
div.holder-export-xls{float:right;
|
25
|
+
a{background:#fafafa;border:1px solid #ddd;border-radius:4px;color:#555;display:block;font-size:11px;margin:0px 0px 0px 0px;padding:7px 15px;text-decoration:none;text-indent:0px;}
|
26
|
+
a:hover{background:#fff;color:#222;}
|
27
|
+
}
|
28
|
+
}
|
29
|
+
|
30
|
+
div.templates{width:100%;}
|
31
|
+
|
32
|
+
div.table{clear:both;display:none;float:left;margin:0px 0px 0px 0px;overflow:hidden;width:100%;
|
33
|
+
table{border-collapse:collapse;border-spacing:0;border:none;border-left:1px solid #ddd;border-right:1px solid #ddd;clear:both;float:left;margin:0px;padding:0px;width:100%;
|
34
|
+
thead{
|
35
|
+
tr{
|
36
|
+
th{border:none;border-left:1px solid #dddddd;border-bottom:1px solid #d1d1d1;border-top:1px solid #e1e1e1;box-shadow:inset 0 1px 0 0 #fff;color:#000;cursor:default;font-family:'Lucida Grande', Helvetica, Arial, sans-serif;font-weight:bold;font-size:13px;height:auto;padding:10px 20px 10px 20px;
|
37
|
+
background: #ececec;
|
38
|
+
background: linear-gradient(top, #F2F2F2, #ececec);
|
39
|
+
background: -moz-linear-gradient(top, #F2F2F2, #ececec);
|
40
|
+
background: -webkit-linear-gradient(top, #F2F2F2, #ececec);
|
41
|
+
}
|
42
|
+
th:first-child{border-left:none;padding-left:30px;}
|
43
|
+
th:last-child{padding-right:30px;}
|
44
|
+
}
|
45
|
+
|
46
|
+
}
|
47
|
+
tbody{
|
48
|
+
tr{
|
49
|
+
td{border-bottom:1px solid #ddd;border-left:none;border-right:none;color:#555;font-size:12px;margin:0px;padding:10px 20px 10px 20px;}
|
50
|
+
td.number{text-align:right;}
|
51
|
+
td:first-child{padding-left:30px;}
|
52
|
+
td:last-child{padding-right:30px;}
|
53
|
+
}
|
54
|
+
tr:hover td{background-color:#fafafa;}
|
55
|
+
}
|
56
|
+
tfoot{
|
57
|
+
tr{
|
58
|
+
td{background-color:#F6F6F6;border-bottom:1px solid #ddd;color:#000;font-size:13px;font-weight:bold;padding:10px 20px;}
|
59
|
+
td.number{text-align:right;}
|
60
|
+
td:first-child{padding-left:30px;}
|
61
|
+
td:last-child{padding-right:30px;}
|
62
|
+
}
|
63
|
+
}
|
64
|
+
}
|
65
|
+
}
|
66
|
+
}
|
@@ -0,0 +1,11 @@
|
|
1
|
+
div.chart-counter{border:1px solid #ddd;border-radius:8px;padding:10px 0px;width:150px;
|
2
|
+
background: #fff;
|
3
|
+
background: linear-gradient(top, #fff, #f5f5f5);
|
4
|
+
background: -moz-linear-gradient(top, #fff, #f5f5f5);
|
5
|
+
background: -webkit-linear-gradient(top, #fff, #f5f5f5);
|
6
|
+
|
7
|
+
h2{display:none;}
|
8
|
+
span{display:block;text-align:center;width:100%;}
|
9
|
+
span.label.primary{color:#999;font-size:11px;margin:0px 0px 3px 0px;}
|
10
|
+
span.value.primary{color:#000;font-family:Arial;font-size:36px;font-weight:bold;}
|
11
|
+
}
|
@@ -0,0 +1,34 @@
|
|
1
|
+
div.chart-donut{clear:none;float:left;margin-left:30px;width:450px;
|
2
|
+
div.tools{background:none;border:none;margin:0px;
|
3
|
+
div.holder-template {margin-left:0px;
|
4
|
+
ul.items{
|
5
|
+
li.chart span.text{background-position:0px 5px;}
|
6
|
+
li.selected.chart span.text{background-position:0px -45px;}
|
7
|
+
}
|
8
|
+
}
|
9
|
+
div.holder-export-xls{margin-right:0px;}
|
10
|
+
}
|
11
|
+
|
12
|
+
div.templates{float:left;
|
13
|
+
div.chart{display:block;height:340px;padding:20px 80px 0px 80px;width:450px;
|
14
|
+
svg{
|
15
|
+
path.slice-1{fill:#10c7df;}
|
16
|
+
path.slice-2{fill:#ffcc00;}
|
17
|
+
path.slice-3{fill:#c1e443;}
|
18
|
+
path.slice-4{fill:#fd41cd;}
|
19
|
+
path.slice-5{fill:#11aec6;}
|
20
|
+
path.slice-6{fill:#e09200;}
|
21
|
+
path.slice-7{fill:#84d200;}
|
22
|
+
path.slice-8{fill:#f2a1e7;}
|
23
|
+
|
24
|
+
text{cursor:default;}
|
25
|
+
.hole{
|
26
|
+
.label{fill:#000;font-size:14px;font-weight:bold;}
|
27
|
+
.text0{fill:#999;font-size:12px;text-transform:uppercase;}
|
28
|
+
}
|
29
|
+
}
|
30
|
+
|
31
|
+
div.tooltip{background-color:#fff;border:1px solid #ddd;border-radius:8;box-shadow:0px 5px 6px -6px #333;color:#000;display:none;height:auto;padding:10px 20px;position:absolute;visibility:visible;width:auto;z-index:100;}
|
32
|
+
}
|
33
|
+
}
|
34
|
+
}
|
@@ -0,0 +1,107 @@
|
|
1
|
+
div.chart-line{clear:both;float:left;width:100%;
|
2
|
+
|
3
|
+
div.tools{margin-left:60px;width:979px;
|
4
|
+
div.holder-step{float:right;margin-right:30px;
|
5
|
+
div.select-field div.current span.text{width:120px;}
|
6
|
+
}
|
7
|
+
|
8
|
+
div.holder-template ul.items{
|
9
|
+
li.chart span.text{background-position:-50px 5px;}
|
10
|
+
li.selected.chart span.text{background-position:-50px -45px;}
|
11
|
+
}
|
12
|
+
}
|
13
|
+
|
14
|
+
div.templates{
|
15
|
+
div.table{margin-left:60px;width:981px;}
|
16
|
+
|
17
|
+
div.chart{clear:both;height:350px;margin-top:-5px;padding:0px 60px 40px 60px;width:1100px;
|
18
|
+
svg{overflow:visible;height:100%;width:100%;
|
19
|
+
rect.bg{ fill:#fff; }
|
20
|
+
|
21
|
+
g.tooltip{
|
22
|
+
rect{border-radius:1em;fill:#fff;stroke:#ccc;}
|
23
|
+
text{cursor:default;fill:#555;font-family:'Arial';
|
24
|
+
tspan.x{font-size:1em;fill:#777;}
|
25
|
+
tspan.y{font-size:1em;fill:#333;}
|
26
|
+
}
|
27
|
+
}
|
28
|
+
|
29
|
+
path{fill:none;}
|
30
|
+
|
31
|
+
g.axis {shape-rendering:crispEdges;
|
32
|
+
line {stroke:#f1f1f1;}
|
33
|
+
.minor {stroke-opacity:.3;}
|
34
|
+
path {stroke:#e1e1e1;}
|
35
|
+
}
|
36
|
+
|
37
|
+
g.pointer{opacity:0;
|
38
|
+
circle{fill:#fff;stroke-width:2;}
|
39
|
+
}
|
40
|
+
g.pointer-1 circle{stroke:#10c7df;}
|
41
|
+
g.pointer-2 circle{stroke:#ffcc00;}
|
42
|
+
g.pointer-3 circle{stroke:#c1e443;}
|
43
|
+
g.pointer-4 circle{stroke:#fd41cd;}
|
44
|
+
g.pointer-5 circle{stroke:#11aec6;}
|
45
|
+
g.pointer-6 circle{stroke:#e09200;}
|
46
|
+
g.pointer-7 circle{stroke:#84d200;}
|
47
|
+
g.pointer-8 circle{stroke:#f2a1e7;}
|
48
|
+
g.pointer.disabled{opacity:0;}
|
49
|
+
|
50
|
+
|
51
|
+
g.line{display:block;width:100%;height:100%;
|
52
|
+
path.stroke{stroke-opacity:1;stroke-width:2;}
|
53
|
+
path.area{fill:none;fill-opacity:.3;}
|
54
|
+
}
|
55
|
+
|
56
|
+
g.line-1{
|
57
|
+
path.stroke{stroke:#10c7df;}
|
58
|
+
path.area{fill:#c7ffff;}
|
59
|
+
}
|
60
|
+
g.line-2 path.stroke{stroke:#ffcc00;}
|
61
|
+
g.line-3 path.stroke{stroke:#c1e443;}
|
62
|
+
g.line-4 path.stroke{stroke:#fd41cd;}
|
63
|
+
g.line-5 path.stroke{stroke:#11aec6;}
|
64
|
+
g.line-6 path.stroke{stroke:#e09200;}
|
65
|
+
g.line-7 path.stroke{stroke:#84d200;}
|
66
|
+
g.line-8 path.stroke{stroke:#f2a1e7;}
|
67
|
+
|
68
|
+
g.line.selected{
|
69
|
+
path.stroke{stroke-width:3;}
|
70
|
+
path.area{fill-opacity:.5;}
|
71
|
+
}
|
72
|
+
|
73
|
+
g.line.hidden{
|
74
|
+
path.stroke{stroke:#f00;}
|
75
|
+
}
|
76
|
+
|
77
|
+
|
78
|
+
g.legend{
|
79
|
+
rect{fill:#fafafa;fill-opacity:.6;}
|
80
|
+
|
81
|
+
g.item{cursor:pointer;
|
82
|
+
text{fill:#555;}
|
83
|
+
line{stroke-width:3;}
|
84
|
+
}
|
85
|
+
g.item:hover{
|
86
|
+
text{fill:#000;}
|
87
|
+
line{stroke-width:4;}
|
88
|
+
}
|
89
|
+
g.item.item-1 line{stroke:#10c7df;}
|
90
|
+
g.item.item-2 line{stroke:#ffcc00;}
|
91
|
+
g.item.item-3 line{stroke:#c1e443;}
|
92
|
+
g.item.item-4 line{stroke:#fd41cd;}
|
93
|
+
g.item.item-5 line{stroke:#11aec6;}
|
94
|
+
g.item.item-6 line{stroke:#e09200;}
|
95
|
+
g.item.item-7 line{stroke:#84d200;}
|
96
|
+
g.item.item-8 line{stroke:#f2a1e7;}
|
97
|
+
|
98
|
+
g.item.disabled{
|
99
|
+
text{fill:#999;}
|
100
|
+
line{stroke-opacity:0.1;}
|
101
|
+
}
|
102
|
+
}
|
103
|
+
}
|
104
|
+
}
|
105
|
+
}
|
106
|
+
|
107
|
+
}
|
@@ -0,0 +1,60 @@
|
|
1
|
+
#*************************************************************************************
|
2
|
+
# TOCOMMENT
|
3
|
+
#*************************************************************************************
|
4
|
+
class CandyChartsController < ApplicationController
|
5
|
+
before_filter :authenticate
|
6
|
+
|
7
|
+
def show
|
8
|
+
if @granted
|
9
|
+
set_default_to if params[:from] and not params[:to]
|
10
|
+
|
11
|
+
name = (params[:id].gsub('-', '_').camelize + 'Chart')
|
12
|
+
|
13
|
+
begin
|
14
|
+
chart = name.constantize.new(params)
|
15
|
+
rescue
|
16
|
+
raise "Chart Candy: You must defined #{name}"
|
17
|
+
end
|
18
|
+
|
19
|
+
builder = "ChartCandy::Builder::#{params[:nature].camelize}".constantize.new(params[:id], params)
|
20
|
+
|
21
|
+
chart.build builder
|
22
|
+
|
23
|
+
respond_to do |format|
|
24
|
+
format.json { render json: builder.to_json }
|
25
|
+
format.xls { render_xls builder }
|
26
|
+
end
|
27
|
+
else
|
28
|
+
respond_to do |format|
|
29
|
+
format.json { render json: { 'state' => 'access_refused' } }
|
30
|
+
format.xls { render text: 'access_refused' }
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def render_xls(builder)
|
36
|
+
spreadsheet = StringIO.new
|
37
|
+
builder.to_xls.write spreadsheet
|
38
|
+
|
39
|
+
send_data spreadsheet.string, filename: builder.filename, type: "application/vnd.ms-excel"
|
40
|
+
end
|
41
|
+
|
42
|
+
def authenticate
|
43
|
+
auth = ChartCandy::Authentication.new(request.url, params)
|
44
|
+
|
45
|
+
@granted = (auth.valid_token? and not auth.expired?)
|
46
|
+
end
|
47
|
+
|
48
|
+
def set_default_to
|
49
|
+
if params[:nature] == 'line'
|
50
|
+
params[:to] = case params[:step]
|
51
|
+
when 'day' then (Time.now.utc - 1.day).end_of_day.iso8601
|
52
|
+
when 'week' then (Time.now.utc - 1.week).end_of_week.iso8601
|
53
|
+
when 'month' then (Time.now.utc - 1.month).end_of_month.iso8601
|
54
|
+
else Time.now.utc.iso8601
|
55
|
+
end
|
56
|
+
else
|
57
|
+
params[:to] = Time.now.utc.iso8601
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
fr:
|
2
|
+
date:
|
3
|
+
abbr_day_names: [lun, mar, mer, jeu, ven, sam, dim]
|
4
|
+
day_names: [dimanche, lundi, mardi, mercredi, jeudi, vendredi, samedi]
|
5
|
+
abbr_month_names: [~, jan, fév, mar, avr, mai, jun, jul, aoû, sep, oct, nov, déc]
|
6
|
+
month_names: [~, janvier, février, mars, avril, mai, juin, juillet, août, septembre, octobre, novembre, décembre]
|
7
|
+
|
8
|
+
chart_candy:
|
9
|
+
base:
|
10
|
+
steps:
|
11
|
+
day: Quotidien
|
12
|
+
week: Hebdomadaire
|
13
|
+
month: Mensuel
|
14
|
+
|
15
|
+
template:
|
16
|
+
table: Tableau
|
17
|
+
chart: Diagramme
|
18
|
+
|
19
|
+
xls_export: "Exporter en Excel"
|
20
|
+
|
21
|
+
date:
|
22
|
+
period:
|
23
|
+
label: Période du
|
24
|
+
label_month: Période de
|
25
|
+
to: au
|
26
|
+
to_month: à
|
27
|
+
week: "Semaine du"
|
28
|
+
|
29
|
+
time:
|
30
|
+
formats:
|
31
|
+
chart_candy_date_without_day: "%B %Y"
|
32
|
+
chart_candy_date_long: "%e %B %Y"
|
33
|
+
chart_candy_date_long_without_year: "%e %B"
|
34
|
+
chart_candy_day: "%e"
|
35
|
+
chart_candy_long: "%e %B %Y à %H:%M"
|
36
|
+
chart_candy_year: "%Y"
|
data/config/routes.rb
ADDED
data/lib/chart-candy.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
module ChartCandy
|
2
|
+
def self.localize(path, options={})
|
3
|
+
options.reverse_merge! format: :date_long
|
4
|
+
|
5
|
+
options[:format] = "chart_candy_#{options[:format]}".to_sym
|
6
|
+
|
7
|
+
return I18n.localize(path, options)
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.translate(path, options={})
|
11
|
+
I18n.translate("chart_candy.#{path}", options)
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.t(path, options={})
|
15
|
+
self.translate path, options
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
require "chart-candy/authentication"
|
20
|
+
require "chart-candy/base_chart"
|
21
|
+
require "chart-candy/builder"
|
22
|
+
require "chart-candy/engine"
|
23
|
+
require 'chart-candy/implants'
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module ChartCandy
|
2
|
+
class Authentication
|
3
|
+
def self.compact_params(original_params)
|
4
|
+
compacted_params = ''
|
5
|
+
|
6
|
+
original_params.each { |k,v| compacted_params << (k.to_s + v.to_s) if not self.reserved_params.include?(k.to_s) }
|
7
|
+
|
8
|
+
return compacted_params
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.reserved_params
|
12
|
+
['action', 'class', 'controller', 'format', 'from', 'nature', 'step', 'to', 'token', 'tools', 'update_every', 'version']
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.tokenize(str)
|
16
|
+
Digest::HMAC.hexdigest(str.chars.sort.join.gsub('/', ''), Rails.configuration.secret_token, Digest::SHA1)
|
17
|
+
#HMAC::SHA1.hexdigest(Rails.configuration.secret_token, str.chars.sort.join.gsub('/', ''))
|
18
|
+
end
|
19
|
+
|
20
|
+
def initialize(request_url, params={})
|
21
|
+
@request_url = request_url
|
22
|
+
@params = params
|
23
|
+
end
|
24
|
+
|
25
|
+
def expired?
|
26
|
+
@params[:timestamp] and Time.parse(@params[:timestamp]) + 12.hours < Time.now
|
27
|
+
end
|
28
|
+
|
29
|
+
def valid_token?
|
30
|
+
@params[:token] == tokenize(filter_url)
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def filter_url
|
36
|
+
filtered_url = @request_url.split('?').first.rpartition('/').first
|
37
|
+
|
38
|
+
return filtered_url + ChartCandy::Authentication.compact_params(@params)
|
39
|
+
end
|
40
|
+
|
41
|
+
def tokenize(str)
|
42
|
+
ChartCandy::Authentication.tokenize(str)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module ChartCandy::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 << ChartCandy.localize(start_at, format: start_at_format)
|
21
|
+
content << (['month', 'year'].include?(options[:step]) ? ChartCandy.t('date.period.to_month') : ChartCandy.t('date.period.to'))
|
22
|
+
content << ChartCandy.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 'chart-candy/builder/base.rb'
|
43
|
+
require 'chart-candy/builder/counter.rb'
|
44
|
+
require 'chart-candy/builder/donut.rb'
|
45
|
+
require 'chart-candy/builder/line.rb'
|
46
|
+
require 'chart-candy/builder/xls_builder.rb'
|