├── .gitignore
├── .travis.yml
├── Gemfile
├── LICENSE.txt
├── README.md
├── Rakefile
├── app
├── assets
│ ├── javascripts
│ │ └── models_stats
│ │ │ ├── graphs
│ │ │ ├── metrics_graph.js.coffee
│ │ │ └── nvd3_graph.js.coffee
│ │ │ ├── metrics_graphics.js
│ │ │ └── nvd3.js
│ └── stylesheets
│ │ └── models_stats
│ │ ├── metrics_graphics.css.scss
│ │ └── nvd3.css
├── helpers
│ └── models_stats
│ │ └── graph_helper.rb
└── views
│ └── models_stats
│ ├── _dashboard.html.erb
│ └── _model_statistics_graph.html.erb
├── doc
└── img
│ ├── mg_example.png
│ ├── nvd3_example.png
│ └── nvd3_users_example.png
├── lib
├── models_stats.rb
├── models_stats
│ ├── statistics.rb
│ ├── statistics_collector.rb
│ └── version.rb
└── tasks
│ └── models_stats.rake
├── models_stats.gemspec
├── spec
├── dummy
│ ├── README.rdoc
│ ├── Rakefile
│ ├── app
│ │ ├── assets
│ │ │ ├── images
│ │ │ │ └── .keep
│ │ │ ├── javascripts
│ │ │ │ └── application.js
│ │ │ └── stylesheets
│ │ │ │ └── application.css
│ │ ├── controllers
│ │ │ ├── application_controller.rb
│ │ │ ├── concerns
│ │ │ │ └── .keep
│ │ │ └── home_controller.rb
│ │ ├── helpers
│ │ │ └── application_helper.rb
│ │ ├── mailers
│ │ │ └── .keep
│ │ ├── models
│ │ │ ├── .keep
│ │ │ ├── comment.rb
│ │ │ ├── concerns
│ │ │ │ └── .keep
│ │ │ ├── profile.rb
│ │ │ └── user.rb
│ │ └── views
│ │ │ ├── home
│ │ │ └── index.html.erb
│ │ │ └── layouts
│ │ │ └── application.html.erb
│ ├── bin
│ │ ├── bundle
│ │ ├── rails
│ │ └── rake
│ ├── config.ru
│ ├── config
│ │ ├── application.rb
│ │ ├── boot.rb
│ │ ├── database.yml
│ │ ├── environment.rb
│ │ ├── environments
│ │ │ ├── development.rb
│ │ │ ├── production.rb
│ │ │ └── test.rb
│ │ ├── initializers
│ │ │ ├── backtrace_silencers.rb
│ │ │ ├── filter_parameter_logging.rb
│ │ │ ├── inflections.rb
│ │ │ ├── mime_types.rb
│ │ │ ├── secret_token.rb
│ │ │ ├── session_store.rb
│ │ │ └── wrap_parameters.rb
│ │ ├── locales
│ │ │ └── en.yml
│ │ ├── models_stats.yml
│ │ ├── models_stats_original.yml
│ │ └── routes.rb
│ ├── db
│ │ ├── migrate
│ │ │ ├── 20141128072012_create_users.rb
│ │ │ ├── 20141228161030_create_comments.rb
│ │ │ └── 20150109120000_create_profiles.rb
│ │ ├── schema.rb
│ │ └── seeds.rb
│ ├── lib
│ │ └── assets
│ │ │ └── .keep
│ ├── log
│ │ └── .keep
│ └── public
│ │ ├── 404.html
│ │ ├── 422.html
│ │ ├── 500.html
│ │ └── favicon.ico
├── helpers
│ └── graph_helper_spec.rb
├── lib
│ └── models_stats
│ │ └── models_stats_spec.rb
├── spec_helper.rb
└── support
│ └── seeds.rb
└── vendor
└── assets
├── javascripts
├── bootstrap.min.js
├── d3.min.js
├── jquery.min.js
├── metricsgraphics.min.js
└── nv.d3.min.js
└── stylesheets
├── bootstrap.min.css
├── metricsgraphics.css
└── nv.d3.min.css
/.gitignore:
--------------------------------------------------------------------------------
1 | /.bundle/
2 | /.yardoc
3 | /Gemfile.lock
4 | /_yardoc/
5 | /coverage/
6 | /pkg/
7 | /spec/reports/
8 | /tmp/
9 | *.bundle
10 | *.so
11 | *.o
12 | *.a
13 | mkmf.log
14 | spec/dummy/db/*.sqlite3
15 | spec/dummy/log/*.log
16 | spec/dummy/tmp/
17 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: ruby
2 | rvm:
3 | - 2.0.0
4 | - 2.1.0
5 | - 2.2.0
6 |
7 | notifications:
8 | email:
9 | - accessd0@gmail.com
10 |
11 | services:
12 | - redis-server
13 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org'
2 |
3 | group :development, :test do
4 | gem "sqlite3"
5 | end
6 |
7 | # Specify your gem's dependencies in models_stats.gemspec
8 | gemspec
9 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Copyright (c) 2014 Andrey Morskov
2 |
3 | MIT License
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining
6 | a copy of this software and associated documentation files (the
7 | "Software"), to deal in the Software without restriction, including
8 | without limitation the rights to use, copy, modify, merge, publish,
9 | distribute, sublicense, and/or sell copies of the Software, and to
10 | permit persons to whom the Software is furnished to do so, subject to
11 | the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be
14 | included in all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ModelsStats
2 |
3 | Graphics for your rails models. It may show count(or average, or sum, or another sql agregate function) of models for each day with grouping, conditions.
4 |
5 | For graphics it uses for your choice [MetricsGraphics.js](https://github.com/mozilla/metrics-graphics) or/and [NVD3](http://nvd3.org/).
6 |
7 | Dependencies: [Redis](http://redis.io/) for store statistics.
8 | [D3](http://d3js.org/), [jQuery](http://jquery.com/), [Bootstrap](http://getbootstrap.com/) it's dependencies of MetricsGraphics.js.
9 |
10 | Preview:
11 |
12 | NVD3:
13 |
14 | 
15 |
16 | 
17 |
18 | MetricsGraphics.js
19 |
20 | 
21 |
22 | ## Installation
23 |
24 | Add this line to your application's Gemfile:
25 |
26 | ```ruby
27 | gem 'models_stats', github: 'accessd/models_stats'
28 | ```
29 |
30 | And then execute:
31 |
32 | $ bundle
33 |
34 | In your application.js manifest:
35 |
36 | //= require models_stats/nvd3
37 |
38 | or/and
39 |
40 | //= require models_stats/metrics_graphics
41 |
42 | if you want use MetricsGraphics.
43 |
44 | In your application.css.scss manifest:
45 |
46 | //= require models_stats/nvd3
47 |
48 | or/and
49 |
50 | //= require models_stats/metrics_graphics
51 |
52 | if you want use MetricsGraphics.
53 |
54 | Also if you use MetricsGraphics.js you must have [jQuery](http://jquery.com/) and [Bootstrap](http://getbootstrap.com/) js/css included.
55 |
56 |
57 | ## Usage
58 |
59 | ### Configuration
60 |
61 | Add config file `config/models_stats.yml`, for example:
62 |
63 | minimal configuration:
64 |
65 | ```yaml
66 | ---
67 | - total_users:
68 | description: "Total users"
69 | model: User
70 | ```
71 |
72 | it would be calculate total users for day.
73 |
74 | Enhanced configuration:
75 |
76 | ```yaml
77 | ---
78 | - total_links_by_error_types: # Statistics alias, must be uniq
79 | description: "Total links by error types"
80 | model: Link
81 | datetime_attr: :created_at # Date or datetime attribute, allows to calculate the count of models per day
82 | group_by: :error_type_id
83 | conditions: "error_type_id != <%= Link::NO_ERROR %>"
84 | group_by_values_map: <%= ModelsStats.convert_hash_to_yaml(Link::ERROR_NAMES) %> # for example maping integer field to text representation
85 | graph_width: 430
86 | graph_height: 140
87 | graphic_lib: nvd3 # By default, or can be metrics_graphics
88 | graphic_type: stacked # It's can be using with nvd3, by default line
89 | date_tick: day # By default, or can be month or week
90 | date_format: '%d/%m' # By default is %x, more information about formattting time available at https://github.com/mbostock/d3/wiki/Time-Formatting
91 | - average_by_keyword_positions:
92 | description: "Average by keyword positions"
93 | select_statement: "AVG(google_position) AS count" # Right here you may specify select query, `count` alias for function required
94 | model: KeywordPosition
95 | ```
96 |
97 | If you want using specific redis for store statistics, set it in `config/initializers/models_stats.rb`, for example:
98 |
99 | ModelsStats.redis_connection = Redis.new(host: '127.0.0.1', port: 6379, db: 5)
100 |
101 | Default graphics library can be configured through:
102 |
103 | ModelsStats.default_lib_for_graphics = :nvd3 # Or metrics_graphics
104 |
105 | Default graphics type:
106 |
107 | ModelsStats.default_graphics_type = :line # Or stacked
108 |
109 | Default graph width:
110 |
111 | ModelsStats.default_graphics_width = 500
112 |
113 | Default graph height:
114 |
115 | ModelsStats.default_graphics_height = 120
116 |
117 | Default date tick:
118 |
119 | ModelsStats.default_date_tick = :day # Or month, week
120 |
121 | Default date format:
122 |
123 | ModelsStats.default_date_format = '%d/%m'
124 |
125 | For the full list of directives for formatting time, refer to [this list](https://github.com/mbostock/d3/wiki/Time-Formatting)
126 |
127 | ### Collecting statistics
128 |
129 | Add to your crontab(may use [whenever](https://github.com/javan/whenever)) rake task `models_stats:collect_stat_for_yesterday`, run it at 00:00 or later and it will collect statistics for yesterday.
130 | For collecting statistics for last month run rake task `models_stats:collect_stat_for_last_month`.
131 | Also you may collect statistics for specific date and config, for example:
132 |
133 | ```ruby
134 | date = 2.days.ago.to_date
135 | statistics_alias = 'total_links_by_error_types' # statistic alias which you define in `config/models_stats.yml`
136 | ModelsStats::StatisticsCollector.new.collect(statistics_alias, date) # By default date is yestarday
137 | ```
138 |
139 | ### Clear statistics
140 |
141 | You may clear statistics data for particular alias.
142 |
143 | ```ruby
144 | c = ModelsStats::StatisticsCollector.new
145 | c.clear_all_data('total_links_by_error_types')
146 | ```
147 |
148 | ### Display graphics
149 |
150 | In your views use helpers:
151 |
152 | Render single graphic for total_links_by_error_types statistic alias(which you define in `config/models_stats.yml`) and last week:
153 |
154 | = render_models_stats_graph('total_links_by_error_types', 1.week, 800, 200) # By default period is 1.month
155 |
156 | With two last parameters you can customize width and height of the chart. In this case it will be 800px and 200px.
157 |
158 | Render all defined graphics splited by two columns - first for new models count, second for total models count
159 |
160 | = render_models_stats_dashboard
161 |
162 | ## Contributing
163 |
164 | 1. Fork it ( https://github.com/accessd/models_stats/fork )
165 | 2. Create your feature branch (`git checkout -b my-new-feature`)
166 | 3. Commit your changes (`git commit -am 'Add some feature'`)
167 | 4. Push to the branch (`git push origin my-new-feature`)
168 | 5. Create a new Pull Request
169 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | require "bundler/gem_tasks"
2 | import "./lib/tasks/models_stats.rake"
3 | require 'rspec/core/rake_task'
4 | task :default => :spec
5 | RSpec::Core::RakeTask.new
6 |
--------------------------------------------------------------------------------
/app/assets/javascripts/models_stats/graphs/metrics_graph.js.coffee:
--------------------------------------------------------------------------------
1 | class @MetricsGraph
2 | constructor: (graph_title, keys, data, stat_alias, date_axis_tick, date_format, width, height) ->
3 | @width = width
4 | @height = height
5 | @graphTitle = graph_title
6 | @statAlias = stat_alias
7 | @dateAxisTick = date_axis_tick
8 | @dateFormat = date_format
9 | @keys = keys
10 | @data = @prepareData(data)
11 | if data.length
12 | @draw()
13 |
14 | prepareData: (rawData) ->
15 | resultData = []
16 |
17 | rawData.forEach (dataForKey) =>
18 | values = convert_dates(dataForKey, 'date')
19 | resultData.push values
20 |
21 | resultData
22 |
23 | draw: ->
24 | placeholder_name = "#{@statAlias}_statistics"
25 | maximums = []
26 | @data.forEach (data) =>
27 | maximum = d3.max data, (d) -> d.date
28 | maximums.push maximum
29 | maxDate = d3.max maximums
30 |
31 | minimums = []
32 | @data.forEach (data) =>
33 | minimum = d3.min data, (d) -> d.date
34 | minimums.push minimum
35 | minDate = d3.max minimums
36 | daysCount = d3.time.day.range(minDate, maxDate, 1).length
37 | switch @dateAxisTick
38 | when 'month'
39 | xax_count = d3.time.month.range(minDate, maxDate, 1).length
40 | when 'week'
41 | xax_count = d3.time.week.range(minDate, maxDate, 1).length
42 | else
43 | xax_count = d3.time.day.range(minDate, maxDate, 1).length
44 |
45 | data_graphic
46 | title: @graphTitle
47 | area: true
48 | legend: @keys
49 | legend_target: ".#{placeholder_name}_legend"
50 | data: @data
51 | width: @width
52 | height: @height
53 | bottom: 0
54 | top: 20
55 | show_years: false
56 | y_extended_ticks: true
57 | xax_count: xax_count
58 | xax_format: (d) =>
59 | df = d3.time.format(@dateFormat)
60 | df(d)
61 | target: "##{placeholder_name}"
62 | x_accessor: 'date'
63 | y_accessor: 'value'
64 |
--------------------------------------------------------------------------------
/app/assets/javascripts/models_stats/graphs/nvd3_graph.js.coffee:
--------------------------------------------------------------------------------
1 | class @Nvd3Graph
2 | constructor: (data, keys, stat_alias, graph_type, date_axis_tick, date_format, width, height) ->
3 | @statAlias = stat_alias
4 | @width = width
5 | @height = height
6 | @keys = keys
7 | @graphType = graph_type
8 | @dateAxisTick = date_axis_tick
9 | @dateFormat = date_format
10 | @data = @prepareData(data)
11 | @draw()
12 |
13 | prepareData: (rawData) ->
14 | resultData = []
15 |
16 | i = 0
17 | rawData.forEach (dataForKey) =>
18 | values = []
19 | dataForKey.forEach (stat) =>
20 | fff = d3.time.format('%Y-%m-%d')
21 | date = fff.parse(stat.date)
22 | values.push [date, stat.value]
23 |
24 | resultData.push {values: values, key: keys[i]}
25 | i++
26 |
27 | resultData
28 |
29 | draw: ->
30 | placeholder_name = "#{@statAlias}_statistics"
31 | graphData = @data
32 | dateFormat = @dateFormat
33 | graphType = @graphType
34 |
35 | if graphData.length
36 | maximums = []
37 | graphData.forEach (data) =>
38 | maximum = d3.max data.values, (d) -> d[1]
39 | maximums.push maximum
40 | maxY = d3.max maximums
41 |
42 | maximums = []
43 | graphData.forEach (data) =>
44 | maximum = d3.max data.values, (d) -> d[0]
45 | maximums.push maximum
46 | maxDate = d3.max maximums
47 |
48 | minimums = []
49 | graphData.forEach (data) =>
50 | minimum = d3.min data.values, (d) -> d[0]
51 | minimums.push minimum
52 | minDate = d3.max minimums
53 |
54 | nv.addGraph =>
55 | if graphType == 'stacked'
56 | chart = nv.models.stackedAreaChart().x((d) -> d[0]).y((d) -> d[1]).useInteractiveGuideline(true)
57 | .transitionDuration(500)
58 | .showControls(true)
59 | .clipEdge(true)
60 | .color(d3.scale.category20().range())
61 | else
62 | chart = nv.models.lineChart().interpolate("basic").x((d) ->
63 | d[0]
64 | ).y((d) ->
65 | d[1]
66 | ).color(d3.scale.category20().range())
67 | chart.width = @width
68 | chart.height = @height
69 |
70 | chart.yAxis.tickFormat(d3.format('f'))
71 |
72 | chart.forceY([0, maxY])
73 | chart.yDomain([0.001, maxY])
74 |
75 | chart.xAxis.tickFormat (d) ->
76 | d3.time.format(dateFormat) new Date(d)
77 |
78 | switch @dateAxisTick
79 | when 'month'
80 | chart.xAxis.tickValues(d3.time.month.range(minDate, maxDate, 1))
81 | when 'week'
82 | chart.xAxis.tickValues(d3.time.week.range(minDate, maxDate, 1))
83 | else
84 | chart.xAxis.tickValues(d3.time.day.range(minDate, maxDate, 1))
85 | d3.select("##{placeholder_name} svg").datum(graphData).transition().duration(500).call(chart)
86 |
87 | nv.utils.windowResize chart.update
88 | chart
89 |
--------------------------------------------------------------------------------
/app/assets/javascripts/models_stats/metrics_graphics.js:
--------------------------------------------------------------------------------
1 | //= require d3.min
2 | //= require metricsgraphics.min
3 | //= require ./graphs/metrics_graph
4 |
--------------------------------------------------------------------------------
/app/assets/javascripts/models_stats/nvd3.js:
--------------------------------------------------------------------------------
1 | //= require d3.min
2 | //= require nv.d3.min
3 | //= require ./graphs/nvd3_graph
4 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/models_stats/metrics_graphics.css.scss:
--------------------------------------------------------------------------------
1 | //= require bootstrap.min
2 | //= require metricsgraphics
3 |
4 | .models_stats_graph .chart_title {
5 | font-size: 15px;
6 | }
7 |
8 | @font-face {
9 | font-family: 'Glyphicons Halflings';
10 | src: font-url('bootstrap/glyphicons-halflings-regular.eot');
11 | src: font-url('bootstrap/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'),
12 | font-url('bootstrap/glyphicons-halflings-regular.woff') format('woff'),
13 | font-url('bootstrap/glyphicons-halflings-regular.ttf') format('truetype'),
14 | font-url('bootstrap/glyphicons-halflings-regular.svg#glyphicons_halflingsregular') format('svg');
15 | }
16 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/models_stats/nvd3.css:
--------------------------------------------------------------------------------
1 | /*
2 | *= require bootstrap.min
3 | *= require nv.d3.min
4 | */
5 |
--------------------------------------------------------------------------------
/app/helpers/models_stats/graph_helper.rb:
--------------------------------------------------------------------------------
1 | module ModelsStats::GraphHelper
2 | def render_models_stats_graph(stat_alias, period = 1.month, width = nil, height = nil)
3 | stat_params = ModelsStats::CONFIG.select{|config| name, params = config.first; name.to_s == stat_alias.to_s}.first
4 | if stat_params
5 | stat_params = stat_params.values[0]
6 | keys, stat_data = ModelsStats::Statistics.for_period(stat_alias, period)
7 | graph_width = if width.present?
8 | width
9 | elsif stat_params["graph_width"].present?
10 | stat_params["graph_width"]
11 | else
12 | ModelsStats.default_graphics_width
13 | end
14 | graph_height = if height.present?
15 | height
16 | elsif stat_params["graph_height"].present?
17 | stat_params["graph_height"]
18 | else
19 | ModelsStats.default_graphics_height
20 | end
21 | if stat_params["graphic_type"].present? && !stat_params["graphic_type"].to_sym.in?(ModelsStats::GRAPHICS_TYPES)
22 | return "Unknown graphic type #{stat_params["graphic_type"]}"
23 | end
24 | graphic_type = stat_params["graphic_type"] || ModelsStats.default_graphics_type
25 | graphic_lib = stat_params["graphic_lib"] || ModelsStats.default_lib_for_graphics
26 | date_tick = stat_params["date_tick"] || ModelsStats.default_date_tick
27 | date_format = stat_params["date_format"] || ModelsStats.default_date_format
28 | render partial: 'models_stats/model_statistics_graph', locals: {graph_title: stat_params["description"] || stat_alias, keys: keys, stat_data: stat_data,
29 | stat_alias: stat_alias, width: graph_width, height: graph_height, graphic_lib: graphic_lib,
30 | graphic_type: graphic_type, date_tick: date_tick, date_format: date_format}
31 | else
32 | "No params for #{stat_alias}"
33 | end
34 | end
35 |
36 | def render_models_stats_dashboard
37 | render partial: 'models_stats/dashboard'
38 | end
39 | end
40 |
--------------------------------------------------------------------------------
/app/views/models_stats/_dashboard.html.erb:
--------------------------------------------------------------------------------
1 |
Models statistics dashboard
2 |
3 | <% ModelsStats::CONFIG.group_by{|params| params.values[0]["datetime_attr"].present?}.each do |for_new_models, models_group| %>
4 |
5 |
<%= for_new_models ? "New models" : "Total models" %>
6 | <% models_group.each do |params| %>
7 | <%= render_models_stats_graph(params.keys[0], 1.month) %>
8 | <% end %>
9 |
10 | <% end %>
11 |
12 |
--------------------------------------------------------------------------------
/app/views/models_stats/_model_statistics_graph.html.erb:
--------------------------------------------------------------------------------
1 |
13 |
14 | <% if stat_data.empty? %>
15 | <%= graph_title %>
16 |
17 | No data
18 |
19 |
20 | <% else %>
21 | <% case graphic_lib.to_s
22 | when 'nvd3' %>
23 | <%= graph_title %>
24 |
25 |
26 |
27 | <% when 'metrics_graphics' %>
28 |
32 | <% else %>
33 | <%= graph_title %>
34 |
35 | Unknown graphics lib
36 |
37 |
38 | <% end %>
39 | <% end %>
40 |
--------------------------------------------------------------------------------
/doc/img/mg_example.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/accessd/models_stats/47482625ede2e9309a85a5d72f2ad0dece37961b/doc/img/mg_example.png
--------------------------------------------------------------------------------
/doc/img/nvd3_example.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/accessd/models_stats/47482625ede2e9309a85a5d72f2ad0dece37961b/doc/img/nvd3_example.png
--------------------------------------------------------------------------------
/doc/img/nvd3_users_example.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/accessd/models_stats/47482625ede2e9309a85a5d72f2ad0dece37961b/doc/img/nvd3_users_example.png
--------------------------------------------------------------------------------
/lib/models_stats.rb:
--------------------------------------------------------------------------------
1 | require "redis-rails"
2 | require "redis-objects"
3 | require "models_stats/statistics"
4 | require "models_stats/statistics_collector"
5 |
6 | module ModelsStats
7 | mattr_accessor :redis_connection, :default_lib_for_graphics, :default_graphics_width, :default_graphics_height, :default_graphics_type,
8 | :default_date_tick, :default_date_format
9 | GRAPHICS_LIBS = [:nvd3, :metrics_graphics]
10 | GRAPHICS_TYPES = [:line, :stacked]
11 |
12 | class Engine < Rails::Engine
13 | initializer "models_stats.load_app_root" do |app|
14 | ModelsStats::CONFIG = YAML.load(ERB.new(File.read(Rails.root.join("config", "models_stats.yml").to_s)).result) rescue []
15 | ModelsStats.check_config
16 | end
17 | end
18 |
19 | self.redis_connection = Redis.current
20 | self.default_lib_for_graphics = :nvd3
21 | self.default_graphics_width = 500
22 | self.default_graphics_height = 120
23 | self.default_graphics_type = :line
24 | self.default_date_tick = 'day'
25 | self.default_date_format = '%x'
26 |
27 | def self.convert_hash_to_yaml(hash)
28 | hash.to_yaml.sub("---", '').gsub("\n", "\n\s\s\s\s\s\s")
29 | end
30 |
31 | def self.check_config
32 | raise IncorrectConfigError, "Please check config, it must be an array" unless CONFIG.is_a?(Array)
33 | end
34 |
35 | class IncorrectConfigError < StandardError;end
36 | end
37 |
--------------------------------------------------------------------------------
/lib/models_stats/statistics.rb:
--------------------------------------------------------------------------------
1 | module ModelsStats
2 | class Statistics
3 | DEFAULT_SELECT_STATEMENT = "COUNT(id) AS count"
4 |
5 | def self.default_group_by_values_map(group_by, model)
6 | model_klass = model.to_s.constantize
7 | if model_klass.respond_to?(:state_machine)
8 | state_machine_states = model_klass.state_machine.states
9 | state_machine_defined = !state_machine_states.first.name.nil?
10 |
11 | if state_machine_defined #&& value_numeric?(group_by)
12 | Hash[model.to_s.constantize.state_machine.states.map{|state| [state.value, state.name]}]
13 | else
14 | {}
15 | end
16 | else
17 | {}
18 | end
19 | end
20 |
21 | def self.value_numeric?(value)
22 | !value.to_s.match(/\A[+-]?\d+?(\.\d+)?\Z/).nil?
23 | end
24 |
25 | def self.for_period(stat_name, period = 1.month)
26 | model_statistics = []
27 |
28 | period.ago.to_date.upto(Date.yesterday) do |date|
29 | stat = read(stat_name, date)
30 | next if stat.empty?
31 |
32 | model_statistics << [date, stat]
33 | end
34 |
35 | all_keys = []
36 | stat = []
37 |
38 | model_statistics.each do |stat|
39 | stat[1].each do |key, count|
40 | models_count = count.to_i
41 | if models_count > 0
42 | all_keys << key unless all_keys.include?(key)
43 | end
44 | end
45 | end
46 |
47 | all_keys.each do |key|
48 | values = []
49 | model_statistics.sort_by! {|value| value[0]}
50 | model_statistics.each do |stat|
51 | date = stat[0]
52 | stat[1].each do |k, count|
53 | if k == key
54 | values << {date: date.to_s, value: count.to_i}
55 | end
56 | end
57 | end
58 |
59 | stat << values
60 | end
61 |
62 | return all_keys, stat
63 | end
64 |
65 | def self.write_day(stat, stat_alias, date)
66 | full_key = full_key(stat_alias, date)
67 | hash = Redis::HashKey.new(full_key, self.redis_connection)
68 | hash.bulk_set(stat)
69 | end
70 |
71 | def self.full_key_matched(stat_alias)
72 | "#{key_prefix}:#{stat_alias}:*"
73 | end
74 |
75 | private
76 |
77 | def self.read(stat_alias, date)
78 | full_key = full_key(stat_alias, date)
79 | hash = Redis::HashKey.new(full_key, self.redis_connection)
80 | hash.all
81 | end
82 |
83 | def self.key_prefix
84 | app_name = Rails.application.class.to_s.split("::").first
85 | "statistics:#{app_name}:models"
86 | end
87 |
88 | def self.full_key(stat_alias, date)
89 | "#{key_prefix}:#{stat_alias}:#{date}"
90 | end
91 |
92 | def self.redis_connection
93 | @@redis_conn ||= ModelsStats.redis_connection
94 | end
95 | end
96 | end
97 |
--------------------------------------------------------------------------------
/lib/models_stats/statistics_collector.rb:
--------------------------------------------------------------------------------
1 | module ModelsStats
2 | class StatisticsCollector
3 | attr_accessor :date
4 |
5 | def clear_all_data(stat_alias)
6 | matched_key = ModelsStats::Statistics.full_key_matched(stat_alias)
7 | keys = ModelsStats.redis_connection.keys(matched_key)
8 | ModelsStats.redis_connection.del(keys)
9 | end
10 |
11 | def collect(stat_alias = nil, date = 1.day.ago.to_date)
12 | self.date = date
13 | stat_params = ModelsStats::CONFIG.select{|params| name, p = params.first; name.to_s == stat_alias.to_s}
14 | models = if stat_params.empty?
15 | ModelsStats::CONFIG
16 | else
17 | stat_params
18 | end
19 | models.each do |config|
20 | name, params = config.first
21 | model = params["model"]
22 | group_by = params["group_by"]
23 | stat_alias = name
24 | group_by_values_map = params["group_by_values_map"] || ModelsStats::Statistics.default_group_by_values_map(group_by, model)
25 | datetime_attr = params["datetime_attr"]
26 | select_statement = params["select_statement"] || ModelsStats::Statistics::DEFAULT_SELECT_STATEMENT
27 |
28 | stat_for_model = stat_for(model, group_by, datetime_attr, params[:conditions], select_statement)
29 |
30 | if stat_for_model.present?
31 | stat_for_model = if group_by.present?
32 | converted_stat = convert_stat(stat_for_model, model, group_by_values_map)
33 | group_by_values_map.each do |index, value|
34 | unless converted_stat.keys.map(&:to_s).include?(value.to_s)
35 | converted_stat[value.to_s] = 0
36 | end
37 | end
38 | converted_stat
39 | else
40 | stat_key = if datetime_attr.present?
41 | "New"
42 | else
43 | "Total"
44 | end
45 | count = stat_for_model.to_a.first.count
46 | {stat_key => count.try(:round)}
47 | end
48 |
49 | ModelsStats::Statistics.write_day(stat_for_model, stat_alias, date)
50 | end
51 | end
52 | end
53 |
54 | def convert_stat(stat_for_model, model, converter)
55 | stat_hash = {}
56 | stat_for_model.each do |s|
57 | group_by_attr_value = s.attributes.values[1]
58 | if [TrueClass, FalseClass].include?(group_by_attr_value.class)
59 | group_by_attr_value = group_by_attr_value.to_s.to_sym
60 | end
61 | key = converter[group_by_attr_value]
62 | stat_hash[key || group_by_attr_value] = s.count
63 | end
64 | stat_hash
65 | end
66 |
67 | private
68 |
69 | def stat_for(model, group_by_attribute, datetime_attr, conditions, select_statement)
70 | model_scope = model.to_s.constantize.unscoped.select(select_statement)
71 | if group_by_attribute.present?
72 | model_scope = model_scope.select("#{group_by_attribute}").group(group_by_attribute)
73 | end
74 |
75 | if datetime_attr.present?
76 | model_scope = model_scope.where(datetime_attr => date.beginning_of_day..date.end_of_day)
77 | end
78 |
79 | if conditions.present?
80 | model_scope = model_scope.where(conditions)
81 | end
82 |
83 | model_scope
84 | end
85 | end
86 | end
87 |
--------------------------------------------------------------------------------
/lib/models_stats/version.rb:
--------------------------------------------------------------------------------
1 | module ModelsStats
2 | VERSION = "0.0.1"
3 | end
4 |
--------------------------------------------------------------------------------
/lib/tasks/models_stats.rake:
--------------------------------------------------------------------------------
1 | namespace :models_stats do
2 | desc "Collect statistics for yesterday"
3 | task :collect_stat_for_yesterday => [:environment] do
4 | collector = ModelsStats::StatisticsCollector.new
5 | collector.collect(nil, Date.yesterday)
6 | end
7 |
8 |
9 | desc "Collect statistics for last month"
10 | task :collect_stat_for_last_month => [:environment] do
11 | collector = ModelsStats::StatisticsCollector.new
12 | 1.month.ago.to_date.upto(Date.yesterday) do |date|
13 | collector.collect(nil, date)
14 | end
15 | end
16 | end
17 |
--------------------------------------------------------------------------------
/models_stats.gemspec:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 | lib = File.expand_path('../lib', __FILE__)
3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4 | require 'models_stats/version'
5 |
6 | Gem::Specification.new do |spec|
7 | spec.name = "models_stats"
8 | spec.version = ModelsStats::VERSION
9 | spec.authors = ["Andrey Morskov"]
10 | spec.email = ["accessd0@gmail.com"]
11 | spec.summary = %q{Graphics for your rails models with MetricsGraphics.js and NVD3}
12 | spec.description = %q{Graphics for your rails models with MetricsGraphics.js and NVD3. It may show count(or average, or sum, or another sql agregate function) of models for each day with grouping, conditions. Uses Redis for store statistics.}
13 | spec.homepage = "https://github.com/accessd/models_stats"
14 | spec.license = "MIT"
15 |
16 | spec.files = `git ls-files -z`.split("\x0")
17 | spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18 | spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19 | spec.require_paths = ["lib"]
20 |
21 | spec.add_dependency "rails", ">= 3.0"
22 | spec.add_dependency "redis-rails"
23 | spec.add_dependency "redis-objects"
24 | spec.add_development_dependency "bundler", "~> 1.7"
25 | spec.add_development_dependency "rake", "~> 10.0"
26 | spec.add_development_dependency "rspec-rails"
27 | spec.add_development_dependency "capybara"
28 | spec.add_development_dependency "factory_girl_rails"
29 | spec.add_development_dependency "database_cleaner"
30 | end
31 |
--------------------------------------------------------------------------------
/spec/dummy/README.rdoc:
--------------------------------------------------------------------------------
1 | == README
2 |
3 | This README would normally document whatever steps are necessary to get the
4 | application up and running.
5 |
6 | Things you may want to cover:
7 |
8 | * Ruby version
9 |
10 | * System dependencies
11 |
12 | * Configuration
13 |
14 | * Database creation
15 |
16 | * Database initialization
17 |
18 | * How to run the test suite
19 |
20 | * Services (job queues, cache servers, search engines, etc.)
21 |
22 | * Deployment instructions
23 |
24 | * ...
25 |
26 |
27 | Please feel free to use a different markup language if you do not plan to run
28 | rake doc:app.
29 |
--------------------------------------------------------------------------------
/spec/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 File.expand_path('../config/application', __FILE__)
5 |
6 | Dummy::Application.load_tasks
7 |
--------------------------------------------------------------------------------
/spec/dummy/app/assets/images/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/accessd/models_stats/47482625ede2e9309a85a5d72f2ad0dece37961b/spec/dummy/app/assets/images/.keep
--------------------------------------------------------------------------------
/spec/dummy/app/assets/javascripts/application.js:
--------------------------------------------------------------------------------
1 | // This is a manifest file that'll be compiled into application.js, which will include all the files
2 | // listed below.
3 | //
4 | // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
5 | // or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path.
6 | //
7 | // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
8 | // compiled file.
9 | //
10 | // Read Sprockets README (https://github.com/sstephenson/sprockets#sprockets-directives) for details
11 | // about supported directives.
12 | //
13 | //= require jquery
14 | //= require jquery_ujs
15 | //= require turbolinks
16 | //= require models_stats/nvd3
17 | //= require models_stats/metrics_graphics
18 | //= require_tree .
19 |
--------------------------------------------------------------------------------
/spec/dummy/app/assets/stylesheets/application.css:
--------------------------------------------------------------------------------
1 | /*
2 | * This is a manifest file that'll be compiled into application.css, which will include all the files
3 | * listed below.
4 | *
5 | * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6 | * or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path.
7 | *
8 | * You're free to add application-wide styles to this file and they'll appear at the bottom of the
9 | * compiled file so the styles you add here take precedence over styles defined in any styles
10 | * defined in the other CSS/SCSS files in this directory. It is generally better to create a new
11 | * file per style scope.
12 | *
13 | *= require_tree .
14 | *= require models_stats/nvd3
15 | *= require models_stats/metrics_graphics
16 | *= require_self
17 | */
18 |
--------------------------------------------------------------------------------
/spec/dummy/app/controllers/application_controller.rb:
--------------------------------------------------------------------------------
1 | class ApplicationController < ActionController::Base
2 | # Prevent CSRF attacks by raising an exception.
3 | # For APIs, you may want to use :null_session instead.
4 | protect_from_forgery with: :exception
5 | end
6 |
--------------------------------------------------------------------------------
/spec/dummy/app/controllers/concerns/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/accessd/models_stats/47482625ede2e9309a85a5d72f2ad0dece37961b/spec/dummy/app/controllers/concerns/.keep
--------------------------------------------------------------------------------
/spec/dummy/app/controllers/home_controller.rb:
--------------------------------------------------------------------------------
1 | class HomeController < ApplicationController
2 | def index
3 |
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/spec/dummy/app/helpers/application_helper.rb:
--------------------------------------------------------------------------------
1 | module ApplicationHelper
2 | end
3 |
--------------------------------------------------------------------------------
/spec/dummy/app/mailers/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/accessd/models_stats/47482625ede2e9309a85a5d72f2ad0dece37961b/spec/dummy/app/mailers/.keep
--------------------------------------------------------------------------------
/spec/dummy/app/models/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/accessd/models_stats/47482625ede2e9309a85a5d72f2ad0dece37961b/spec/dummy/app/models/.keep
--------------------------------------------------------------------------------
/spec/dummy/app/models/comment.rb:
--------------------------------------------------------------------------------
1 | class Comment < ActiveRecord::Base
2 | end
3 |
--------------------------------------------------------------------------------
/spec/dummy/app/models/concerns/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/accessd/models_stats/47482625ede2e9309a85a5d72f2ad0dece37961b/spec/dummy/app/models/concerns/.keep
--------------------------------------------------------------------------------
/spec/dummy/app/models/profile.rb:
--------------------------------------------------------------------------------
1 | class Profile < ActiveRecord::Base
2 | end
3 |
--------------------------------------------------------------------------------
/spec/dummy/app/models/user.rb:
--------------------------------------------------------------------------------
1 | class User < ActiveRecord::Base
2 |
3 | end
4 |
--------------------------------------------------------------------------------
/spec/dummy/app/views/home/index.html.erb:
--------------------------------------------------------------------------------
1 | <%= render_models_stats_graph('total_users', 1.year) %>
2 |
--------------------------------------------------------------------------------
/spec/dummy/app/views/layouts/application.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Dummy
5 | <%= stylesheet_link_tag "application", media: "all", "data-turbolinks-track" => true %>
6 | <%= javascript_include_tag "application", "data-turbolinks-track" => true %>
7 | <%= csrf_meta_tags %>
8 |
9 |
10 |
11 | <%= yield %>
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/spec/dummy/bin/bundle:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
3 | load Gem.bin_path('bundler', 'bundle')
4 |
--------------------------------------------------------------------------------
/spec/dummy/bin/rails:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | APP_PATH = File.expand_path('../../config/application', __FILE__)
3 | require_relative '../config/boot'
4 | require 'rails/commands'
5 |
--------------------------------------------------------------------------------
/spec/dummy/bin/rake:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | require_relative '../config/boot'
3 | require 'rake'
4 | Rake.application.run
5 |
--------------------------------------------------------------------------------
/spec/dummy/config.ru:
--------------------------------------------------------------------------------
1 | # This file is used by Rack-based servers to start the application.
2 |
3 | require ::File.expand_path('../config/environment', __FILE__)
4 | run Rails.application
5 |
--------------------------------------------------------------------------------
/spec/dummy/config/application.rb:
--------------------------------------------------------------------------------
1 | require File.expand_path('../boot', __FILE__)
2 |
3 | # Pick the frameworks you want:
4 | require "active_record/railtie"
5 | require "action_controller/railtie"
6 | require "action_mailer/railtie"
7 | require "sprockets/railtie"
8 | # require "rails/test_unit/railtie"
9 |
10 | Bundler.require(*Rails.groups)
11 | require "models_stats"
12 |
13 | module Dummy
14 | class Application < Rails::Application
15 | # Settings in config/environments/* take precedence over those specified here.
16 | # Application configuration should go into files in config/initializers
17 | # -- all .rb files in that directory are automatically loaded.
18 |
19 | # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
20 | # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC.
21 | # config.time_zone = 'Central Time (US & Canada)'
22 |
23 | # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
24 | # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
25 | # config.i18n.default_locale = :de
26 | end
27 | end
28 |
29 |
--------------------------------------------------------------------------------
/spec/dummy/config/boot.rb:
--------------------------------------------------------------------------------
1 | # Set up gems listed in the Gemfile.
2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../../../Gemfile', __FILE__)
3 |
4 | require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE'])
5 | $LOAD_PATH.unshift File.expand_path('../../../../lib', __FILE__)
6 |
--------------------------------------------------------------------------------
/spec/dummy/config/database.yml:
--------------------------------------------------------------------------------
1 | # SQLite version 3.x
2 | # gem install sqlite3
3 | #
4 | # Ensure the SQLite 3 gem is defined in your Gemfile
5 | # gem 'sqlite3'
6 | development:
7 | adapter: sqlite3
8 | database: db/development.sqlite3
9 | pool: 5
10 | timeout: 5000
11 |
12 | # Warning: The database defined as "test" will be erased and
13 | # re-generated from your development database when you run "rake".
14 | # Do not set this db to the same as development or production.
15 | test:
16 | adapter: sqlite3
17 | database: ":memory:"
18 | timeout: 500
19 |
20 | production:
21 | adapter: sqlite3
22 | database: db/production.sqlite3
23 | pool: 5
24 | timeout: 5000
25 |
--------------------------------------------------------------------------------
/spec/dummy/config/environment.rb:
--------------------------------------------------------------------------------
1 | # Load the Rails application.
2 | require File.expand_path('../application', __FILE__)
3 |
4 | # Initialize the Rails application.
5 | Dummy::Application.initialize!
6 |
--------------------------------------------------------------------------------
/spec/dummy/config/environments/development.rb:
--------------------------------------------------------------------------------
1 | Dummy::Application.configure do
2 | # Settings specified here will take precedence over those in config/application.rb.
3 |
4 | # In the development environment your application's code is reloaded on
5 | # every request. This slows down response time but is perfect for development
6 | # since you don't have to restart the web server when you make code changes.
7 | config.cache_classes = false
8 |
9 | # Do not eager load code on boot.
10 | config.eager_load = false
11 |
12 | # Show full error reports and disable caching.
13 | config.consider_all_requests_local = true
14 | config.action_controller.perform_caching = false
15 |
16 | # Don't care if the mailer can't send.
17 | config.action_mailer.raise_delivery_errors = false
18 |
19 | # Print deprecation notices to the Rails logger.
20 | config.active_support.deprecation = :log
21 |
22 | # Raise an error on page load if there are pending migrations
23 | config.active_record.migration_error = :page_load
24 |
25 | # Debug mode disables concatenation and preprocessing of assets.
26 | # This option may cause significant delays in view rendering with a large
27 | # number of complex assets.
28 | config.assets.debug = true
29 |
30 | REDIS_URL = "redis://127.0.0.1:6379/2"
31 | config.cache_store = :redis_store, REDIS_URL
32 | end
33 |
--------------------------------------------------------------------------------
/spec/dummy/config/environments/production.rb:
--------------------------------------------------------------------------------
1 | Dummy::Application.configure do
2 | # Settings specified here will take precedence over those in config/application.rb.
3 |
4 | # Code is not reloaded between requests.
5 | config.cache_classes = true
6 |
7 | # Eager load code on boot. This eager loads most of Rails and
8 | # your application in memory, allowing both thread web servers
9 | # and those relying on copy on write to perform better.
10 | # Rake tasks automatically ignore this option for performance.
11 | config.eager_load = true
12 |
13 | # Full error reports are disabled and caching is turned on.
14 | config.consider_all_requests_local = false
15 | config.action_controller.perform_caching = true
16 |
17 | # Enable Rack::Cache to put a simple HTTP cache in front of your application
18 | # Add `rack-cache` to your Gemfile before enabling this.
19 | # For large-scale production use, consider using a caching reverse proxy like nginx, varnish or squid.
20 | # config.action_dispatch.rack_cache = true
21 |
22 | # Disable Rails's static asset server (Apache or nginx will already do this).
23 | config.serve_static_assets = false
24 |
25 | # Compress JavaScripts and CSS.
26 | config.assets.js_compressor = :uglifier
27 | # config.assets.css_compressor = :sass
28 |
29 | # Do not fallback to assets pipeline if a precompiled asset is missed.
30 | config.assets.compile = false
31 |
32 | # Generate digests for assets URLs.
33 | config.assets.digest = true
34 |
35 | # Version of your assets, change this if you want to expire all your assets.
36 | config.assets.version = '1.0'
37 |
38 | # Specifies the header that your server uses for sending files.
39 | # config.action_dispatch.x_sendfile_header = "X-Sendfile" # for apache
40 | # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for nginx
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 | # Set to :debug to see everything in the log.
46 | config.log_level = :info
47 |
48 | # Prepend all log lines with the following tags.
49 | # config.log_tags = [ :subdomain, :uuid ]
50 |
51 | # Use a different logger for distributed setups.
52 | # config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new)
53 |
54 | # Use a different cache store in production.
55 | # config.cache_store = :mem_cache_store
56 |
57 | # Enable serving of images, stylesheets, and JavaScripts from an asset server.
58 | # config.action_controller.asset_host = "http://assets.example.com"
59 |
60 | # Precompile additional assets.
61 | # application.js, application.css, and all non-JS/CSS in app/assets folder are already added.
62 | # config.assets.precompile += %w( search.js )
63 |
64 | # Ignore bad email addresses and do not raise email delivery errors.
65 | # Set this to true and configure the email server for immediate delivery to raise delivery errors.
66 | # config.action_mailer.raise_delivery_errors = false
67 |
68 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to
69 | # the I18n.default_locale when a translation can not be found).
70 | config.i18n.fallbacks = true
71 |
72 | # Send deprecation notices to registered listeners.
73 | config.active_support.deprecation = :notify
74 |
75 | # Disable automatic flushing of the log to improve performance.
76 | # config.autoflush_log = false
77 |
78 | # Use default logging formatter so that PID and timestamp are not suppressed.
79 | config.log_formatter = ::Logger::Formatter.new
80 | end
81 |
--------------------------------------------------------------------------------
/spec/dummy/config/environments/test.rb:
--------------------------------------------------------------------------------
1 | Dummy::Application.configure do
2 | # Settings specified here will take precedence over those in config/application.rb.
3 |
4 | # The test environment is used exclusively to run your application's
5 | # test suite. You never need to work with it otherwise. Remember that
6 | # your test database is "scratch space" for the test suite and is wiped
7 | # and recreated between test runs. Don't rely on the data there!
8 | config.cache_classes = true
9 |
10 | # Do not eager load code on boot. This avoids loading your whole application
11 | # just for the purpose of running a single test. If you are using a tool that
12 | # preloads Rails for running tests, you may have to set it to true.
13 | config.eager_load = false
14 |
15 | # Configure static asset server for tests with Cache-Control for performance.
16 | config.serve_static_assets = true
17 | config.static_cache_control = "public, max-age=3600"
18 |
19 | # Show full error reports and disable caching.
20 | config.consider_all_requests_local = true
21 | config.action_controller.perform_caching = false
22 |
23 | # Raise exceptions instead of rendering exception templates.
24 | config.action_dispatch.show_exceptions = false
25 |
26 | # Disable request forgery protection in test environment.
27 | config.action_controller.allow_forgery_protection = false
28 |
29 | # Tell Action Mailer not to deliver emails to the real world.
30 | # The :test delivery method accumulates sent emails in the
31 | # ActionMailer::Base.deliveries array.
32 | config.action_mailer.delivery_method = :test
33 |
34 | # Print deprecation notices to the stderr.
35 | config.active_support.deprecation = :stderr
36 |
37 | REDIS_URL = "redis://127.0.0.1:6379/2"
38 | config.cache_store = :redis_store, REDIS_URL
39 | end
40 |
--------------------------------------------------------------------------------
/spec/dummy/config/initializers/backtrace_silencers.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces.
4 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ }
5 |
6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code.
7 | # Rails.backtrace_cleaner.remove_silencers!
8 |
--------------------------------------------------------------------------------
/spec/dummy/config/initializers/filter_parameter_logging.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Configure sensitive parameters which will be filtered from the log file.
4 | Rails.application.config.filter_parameters += [:password]
5 |
--------------------------------------------------------------------------------
/spec/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 |
--------------------------------------------------------------------------------
/spec/dummy/config/initializers/mime_types.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Add new mime types for use in respond_to blocks:
4 | # Mime::Type.register "text/richtext", :rtf
5 | # Mime::Type.register_alias "text/html", :iphone
6 |
--------------------------------------------------------------------------------
/spec/dummy/config/initializers/secret_token.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Your secret key is used for verifying the integrity of signed cookies.
4 | # If you change this key, all old signed cookies will become invalid!
5 |
6 | # Make sure the secret is at least 30 characters and all random,
7 | # no regular words or you'll be exposed to dictionary attacks.
8 | # You can use `rake secret` to generate a secure secret key.
9 |
10 | # Make sure your secret_key_base is kept private
11 | # if you're sharing your code publicly.
12 | Dummy::Application.config.secret_key_base = '0961f828e6e86076dd6bb5ef0d3ec074fb5ce329ea3ca2b779b5947533ab4f1ae9269dc844b8924c8791ba35fae0a4cc56bcf1f4258fb1b02b94846470124289'
13 |
--------------------------------------------------------------------------------
/spec/dummy/config/initializers/session_store.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | Dummy::Application.config.session_store :cookie_store, key: '_dummy_session'
4 |
--------------------------------------------------------------------------------
/spec/dummy/config/initializers/wrap_parameters.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # This file contains settings for ActionController::ParamsWrapper which
4 | # is enabled by default.
5 |
6 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array.
7 | ActiveSupport.on_load(:action_controller) do
8 | wrap_parameters format: [:json] if respond_to?(:wrap_parameters)
9 | end
10 |
11 | # To enable root element in JSON for ActiveRecord objects.
12 | # ActiveSupport.on_load(:active_record) do
13 | # self.include_root_in_json = true
14 | # end
15 |
--------------------------------------------------------------------------------
/spec/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 | # To learn more, please read the Rails Internationalization guide
20 | # available at http://guides.rubyonrails.org/i18n.html.
21 |
22 | en:
23 | hello: "Hello world"
24 |
--------------------------------------------------------------------------------
/spec/dummy/config/models_stats.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - total_users:
3 | description: "Total users"
4 | model: User
5 | datetime_attr: created_at
6 | group_by: state
7 | group_by_values_map: <%= ModelsStats.convert_hash_to_yaml({0 => 'new', 1 => 'ok'}) %>
8 | graphic_lib: metrics_graphics
9 | graphic_type: stacked
10 | graph_width: 1500
11 | graph_height: 200
12 | date_format: '%d/%m'
13 | date_tick: month
14 | - total_comments:
15 | description: "Total comments"
16 | model: Comment
17 | - total_profiles:
18 | description: "Total profiles"
19 | model: Profile
20 | graphic_type: stack
21 |
--------------------------------------------------------------------------------
/spec/dummy/config/models_stats_original.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - total_users:
3 | description: "Total users"
4 | model: User
5 | datetime_attr: created_at
6 | group_by: state
7 | group_by_values_map: <%= ModelsStats.convert_hash_to_yaml({0 => 'new', 1 => 'ok'}) %>
8 | graphic_lib: metrics_graphics
9 | graphic_type: stacked
10 | graph_width: 1500
11 | graph_height: 200
12 | date_format: '%d/%m'
13 | date_tick: month
14 | - total_comments:
15 | description: "Total comments"
16 | model: Comment
17 | - total_profiles:
18 | description: "Total profiles"
19 | model: Profile
20 | graphic_type: stack
21 |
--------------------------------------------------------------------------------
/spec/dummy/config/routes.rb:
--------------------------------------------------------------------------------
1 | Dummy::Application.routes.draw do
2 | root 'home#index'
3 | end
4 |
--------------------------------------------------------------------------------
/spec/dummy/db/migrate/20141128072012_create_users.rb:
--------------------------------------------------------------------------------
1 | class CreateUsers < ActiveRecord::Migration
2 | def change
3 | create_table :users do |t|
4 | t.string :name
5 | t.string :type_of
6 | t.integer :state
7 | t.timestamps
8 | end
9 | end
10 | end
11 |
--------------------------------------------------------------------------------
/spec/dummy/db/migrate/20141228161030_create_comments.rb:
--------------------------------------------------------------------------------
1 | class CreateComments < ActiveRecord::Migration
2 | def change
3 | create_table :comments do |t|
4 | t.text :text
5 | t.integer :state
6 | t.timestamps
7 | end
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/spec/dummy/db/migrate/20150109120000_create_profiles.rb:
--------------------------------------------------------------------------------
1 | class CreateProfiles < ActiveRecord::Migration
2 | def change
3 | create_table :profiles do |t|
4 | end
5 | end
6 | end
7 |
--------------------------------------------------------------------------------
/spec/dummy/db/schema.rb:
--------------------------------------------------------------------------------
1 | # encoding: UTF-8
2 | # This file is auto-generated from the current state of the database. Instead
3 | # of editing this file, please use the migrations feature of Active Record to
4 | # incrementally modify your database, and then regenerate this schema definition.
5 | #
6 | # Note that this schema.rb definition is the authoritative source for your
7 | # database schema. If you need to create the application database on another
8 | # system, you should be using db:schema:load, not running all the migrations
9 | # from scratch. The latter is a flawed and unsustainable approach (the more migrations
10 | # you'll amass, the slower it'll run and the greater likelihood for issues).
11 | #
12 | # It's strongly recommended that you check this file into your version control system.
13 |
14 | ActiveRecord::Schema.define(version: 20140320182332) do
15 |
16 | create_table "users", force: true do |t|
17 | t.string "name"
18 | t.string "type_of"
19 | t.integer "state"
20 | t.datetime "created_at"
21 | t.datetime "updated_at"
22 | end
23 |
24 | create_table "comments", force: true do |t|
25 | t.text "text"
26 | t.integer "state"
27 | t.datetime "created_at"
28 | t.datetime "updated_at"
29 | end
30 |
31 | create_table "profiles", force: true do |t|
32 | end
33 |
34 | end
35 |
--------------------------------------------------------------------------------
/spec/dummy/db/seeds.rb:
--------------------------------------------------------------------------------
1 | # This file should contain all the record creation needed to seed the database with its default values.
2 | # The data can then be loaded with the rake db:seed (or created alongside the db with db:setup).
3 | #
4 | # Examples:
5 | #
6 | # cities = City.create([{ name: 'Chicago' }, { name: 'Copenhagen' }])
7 | # Mayor.create(name: 'Emanuel', city: cities.first)
8 |
9 | # 10000.times do
10 | # User.create(name: 'test', created_at: "2014-#{rand(1..11)}-#{rand(1..30)}", type_of: ["admin", "plain"].sample, state: [0, 1].sample)
11 | # end
12 |
--------------------------------------------------------------------------------
/spec/dummy/lib/assets/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/accessd/models_stats/47482625ede2e9309a85a5d72f2ad0dece37961b/spec/dummy/lib/assets/.keep
--------------------------------------------------------------------------------
/spec/dummy/log/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/accessd/models_stats/47482625ede2e9309a85a5d72f2ad0dece37961b/spec/dummy/log/.keep
--------------------------------------------------------------------------------
/spec/dummy/public/404.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | The page you were looking for doesn't exist (404)
5 |
48 |
49 |
50 |
51 |
52 |
53 |
The page you were looking for doesn't exist.
54 |
You may have mistyped the address or the page may have moved.
55 |
56 | If you are the application owner check the logs for more information.
57 |
58 |
59 |
--------------------------------------------------------------------------------
/spec/dummy/public/422.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | The change you wanted was rejected (422)
5 |
48 |
49 |
50 |
51 |
52 |
53 |
The change you wanted was rejected.
54 |
Maybe you tried to change something you didn't have access to.
55 |
56 | If you are the application owner check the logs for more information.
57 |
58 |
59 |
--------------------------------------------------------------------------------
/spec/dummy/public/500.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | We're sorry, but something went wrong (500)
5 |
48 |
49 |
50 |
51 |
52 |
53 |
We're sorry, but something went wrong.
54 |
55 | If you are the application owner check the logs for more information.
56 |
57 |
58 |
--------------------------------------------------------------------------------
/spec/dummy/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/accessd/models_stats/47482625ede2e9309a85a5d72f2ad0dece37961b/spec/dummy/public/favicon.ico
--------------------------------------------------------------------------------
/spec/helpers/graph_helper_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe ModelsStats::GraphHelper, type: :helper do
4 | describe "#render_models_stats_graph" do
5 | it "returns message if params doesn't exists for specific statistics" do
6 | expect(helper.render_models_stats_graph('total_posts')).to eq('No params for total_posts')
7 | end
8 |
9 | it "render partial with graph if stat params defined" do
10 | collector = ModelsStats::StatisticsCollector.new
11 | 1.month.ago.to_date.upto(Date.yesterday) do |date|
12 | collector.collect(nil, date)
13 | end
14 | helper = ApplicationController.helpers
15 | params = {partial: 'models_stats/model_statistics_graph', locals: {graph_title: 'Total users', keys: ['new', 'ok'], stat_data: stat_data,
16 | stat_alias: 'total_users', width: 1500, height: 200, graphic_lib: 'metrics_graphics',
17 | graphic_type: 'stacked', date_tick: 'month', date_format: '%d/%m'}}
18 | expect(helper).to receive(:render).with(params)
19 | helper.render_models_stats_graph('total_users')
20 | end
21 |
22 | it "returns message if not known graphic type passed" do
23 | expect(helper.render_models_stats_graph('total_profiles')).to eq('Unknown graphic type stack')
24 | end
25 | end
26 | end
27 |
--------------------------------------------------------------------------------
/spec/lib/models_stats/models_stats_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe ModelsStats do
4 | describe "checking config for correctness" do
5 | it "exception if config not an array" do
6 | config_file = File.expand_path("../../../dummy/config/models_stats.yml", __FILE__)
7 | File.open(config_file, 'w') { |file| file.write("total_users:\n\s\sstat_alias: total_users") }
8 | expect do
9 | run_initializer
10 | end.to raise_error(ModelsStats::IncorrectConfigError, "Please check config, it must be an array")
11 | original_config_file = File.expand_path("../../../dummy/config/models_stats_original.yml", __FILE__)
12 | FileUtils.cp(original_config_file, config_file)
13 | run_initializer
14 | end
15 |
16 | it "no exception if config is array" do
17 | expect do
18 | run_initializer
19 | end.not_to raise_error
20 | end
21 | end
22 |
23 | begin "Helpers"
24 | def run_initializer
25 | initializer = ModelsStats::Engine.initializers.select { |i| i.name == "models_stats.load_app_root" }.first
26 | initializer.run(Dummy::Application)
27 | end
28 | end
29 | end
30 |
--------------------------------------------------------------------------------
/spec/spec_helper.rb:
--------------------------------------------------------------------------------
1 | ENV['RAILS_ENV'] ||= 'test'
2 |
3 | require File.expand_path("../dummy/config/environment.rb", __FILE__)
4 | require 'rspec/rails'
5 | require 'capybara/rails'
6 | require 'capybara/rspec'
7 | require 'factory_girl_rails'
8 | require 'database_cleaner'
9 |
10 | load "#{Rails.root.to_s}/db/schema.rb" # set up memory db
11 |
12 | Rails.backtrace_cleaner.remove_silencers!
13 |
14 | # Load support files
15 | Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
16 |
17 | RSpec.configure do |config|
18 | config.mock_with :rspec
19 | config.use_transactional_fixtures = true
20 | config.infer_base_class_for_anonymous_controllers = false
21 | config.order = "random"
22 |
23 | config.after(:each, :type => :request) do
24 | Capybara.reset_sessions! # Forget the (simulated) browser state
25 | Capybara.use_default_driver # Revert Capybara.current_driver to Capybara.default_driver
26 | end
27 |
28 | config.before(:suite) do
29 | Rails.cache.clear
30 | puts "Truncating database"
31 | DatabaseCleaner.clean_with(:truncation)
32 |
33 | puts "Seeding data"
34 | Seed.all
35 | end
36 |
37 | config.after(:suite) do
38 | DatabaseCleaner.clean_with(:truncation)
39 | end
40 | end
41 |
42 | def stat_data
43 | [[{:date=>"2014-12-15", :value=>9}, {:date=>"2015-01-01", :value=>10}, {:date=>"2015-01-03", :value=>0}], [{:date=>"2014-12-15", :value=>0}, {:date=>"2015-01-01", :value=>0}, {:date=>"2015-01-03", :value=>4}]]
44 | end
45 |
--------------------------------------------------------------------------------
/spec/support/seeds.rb:
--------------------------------------------------------------------------------
1 | class Seed
2 | def self.all
3 | 10.times do
4 | User.create!(name: 'test', created_at: "#{Date.current.year}-#{Date.current.month}-1", type_of: "admin", state: 0)
5 | end
6 |
7 | 4.times do
8 | User.create!(name: 'test', created_at: "#{Date.current.year}-#{Date.current.month}-3", type_of: "plain", state: 1)
9 | end
10 |
11 | 9.times do
12 | User.create!(name: 'test', created_at: "#{Date.current.year}-#{Date.current.month}-15", type_of: "plain", state: 0)
13 | end
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/vendor/assets/javascripts/bootstrap.min.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * Bootstrap v3.2.0 (http://getbootstrap.com)
3 | * Copyright 2011-2014 Twitter, Inc.
4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
5 | */
6 | if("undefined"==typeof jQuery)throw new Error("Bootstrap's JavaScript requires jQuery");+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]};return!1}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one("bsTransitionEnd",function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b(),a.support.transition&&(a.event.special.bsTransitionEnd={bindType:a.support.transition.end,delegateType:a.support.transition.end,handle:function(b){return a(b.target).is(this)?b.handleObj.handler.apply(this,arguments):void 0}})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var c=a(this),e=c.data("bs.alert");e||c.data("bs.alert",e=new d(this)),"string"==typeof b&&e[b].call(c)})}var c='[data-dismiss="alert"]',d=function(b){a(b).on("click",c,this.close)};d.VERSION="3.2.0",d.prototype.close=function(b){function c(){f.detach().trigger("closed.bs.alert").remove()}var d=a(this),e=d.attr("data-target");e||(e=d.attr("href"),e=e&&e.replace(/.*(?=#[^\s]*$)/,""));var f=a(e);b&&b.preventDefault(),f.length||(f=d.hasClass("alert")?d:d.parent()),f.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(f.removeClass("in"),a.support.transition&&f.hasClass("fade")?f.one("bsTransitionEnd",c).emulateTransitionEnd(150):c())};var e=a.fn.alert;a.fn.alert=b,a.fn.alert.Constructor=d,a.fn.alert.noConflict=function(){return a.fn.alert=e,this},a(document).on("click.bs.alert.data-api",c,d.prototype.close)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof b&&b;e||d.data("bs.button",e=new c(this,f)),"toggle"==b?e.toggle():b&&e.setState(b)})}var c=function(b,d){this.$element=a(b),this.options=a.extend({},c.DEFAULTS,d),this.isLoading=!1};c.VERSION="3.2.0",c.DEFAULTS={loadingText:"loading..."},c.prototype.setState=function(b){var c="disabled",d=this.$element,e=d.is("input")?"val":"html",f=d.data();b+="Text",null==f.resetText&&d.data("resetText",d[e]()),d[e](null==f[b]?this.options[b]:f[b]),setTimeout(a.proxy(function(){"loadingText"==b?(this.isLoading=!0,d.addClass(c).attr(c,c)):this.isLoading&&(this.isLoading=!1,d.removeClass(c).removeAttr(c))},this),0)},c.prototype.toggle=function(){var a=!0,b=this.$element.closest('[data-toggle="buttons"]');if(b.length){var c=this.$element.find("input");"radio"==c.prop("type")&&(c.prop("checked")&&this.$element.hasClass("active")?a=!1:b.find(".active").removeClass("active")),a&&c.prop("checked",!this.$element.hasClass("active")).trigger("change")}a&&this.$element.toggleClass("active")};var d=a.fn.button;a.fn.button=b,a.fn.button.Constructor=c,a.fn.button.noConflict=function(){return a.fn.button=d,this},a(document).on("click.bs.button.data-api",'[data-toggle^="button"]',function(c){var d=a(c.target);d.hasClass("btn")||(d=d.closest(".btn")),b.call(d,"toggle"),c.preventDefault()})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},c.DEFAULTS,d.data(),"object"==typeof b&&b),g="string"==typeof b?b:f.slide;e||d.data("bs.carousel",e=new c(this,f)),"number"==typeof b?e.to(b):g?e[g]():f.interval&&e.pause().cycle()})}var c=function(b,c){this.$element=a(b).on("keydown.bs.carousel",a.proxy(this.keydown,this)),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=this.sliding=this.interval=this.$active=this.$items=null,"hover"==this.options.pause&&this.$element.on("mouseenter.bs.carousel",a.proxy(this.pause,this)).on("mouseleave.bs.carousel",a.proxy(this.cycle,this))};c.VERSION="3.2.0",c.DEFAULTS={interval:5e3,pause:"hover",wrap:!0},c.prototype.keydown=function(a){switch(a.which){case 37:this.prev();break;case 39:this.next();break;default:return}a.preventDefault()},c.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},c.prototype.getItemIndex=function(a){return this.$items=a.parent().children(".item"),this.$items.index(a||this.$active)},c.prototype.to=function(b){var c=this,d=this.getItemIndex(this.$active=this.$element.find(".item.active"));return b>this.$items.length-1||0>b?void 0:this.sliding?this.$element.one("slid.bs.carousel",function(){c.to(b)}):d==b?this.pause().cycle():this.slide(b>d?"next":"prev",a(this.$items[b]))},c.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},c.prototype.next=function(){return this.sliding?void 0:this.slide("next")},c.prototype.prev=function(){return this.sliding?void 0:this.slide("prev")},c.prototype.slide=function(b,c){var d=this.$element.find(".item.active"),e=c||d[b](),f=this.interval,g="next"==b?"left":"right",h="next"==b?"first":"last",i=this;if(!e.length){if(!this.options.wrap)return;e=this.$element.find(".item")[h]()}if(e.hasClass("active"))return this.sliding=!1;var j=e[0],k=a.Event("slide.bs.carousel",{relatedTarget:j,direction:g});if(this.$element.trigger(k),!k.isDefaultPrevented()){if(this.sliding=!0,f&&this.pause(),this.$indicators.length){this.$indicators.find(".active").removeClass("active");var l=a(this.$indicators.children()[this.getItemIndex(e)]);l&&l.addClass("active")}var m=a.Event("slid.bs.carousel",{relatedTarget:j,direction:g});return a.support.transition&&this.$element.hasClass("slide")?(e.addClass(b),e[0].offsetWidth,d.addClass(g),e.addClass(g),d.one("bsTransitionEnd",function(){e.removeClass([b,g].join(" ")).addClass("active"),d.removeClass(["active",g].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger(m)},0)}).emulateTransitionEnd(1e3*d.css("transition-duration").slice(0,-1))):(d.removeClass("active"),e.addClass("active"),this.sliding=!1,this.$element.trigger(m)),f&&this.cycle(),this}};var d=a.fn.carousel;a.fn.carousel=b,a.fn.carousel.Constructor=c,a.fn.carousel.noConflict=function(){return a.fn.carousel=d,this},a(document).on("click.bs.carousel.data-api","[data-slide], [data-slide-to]",function(c){var d,e=a(this),f=a(e.attr("data-target")||(d=e.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""));if(f.hasClass("carousel")){var g=a.extend({},f.data(),e.data()),h=e.attr("data-slide-to");h&&(g.interval=!1),b.call(f,g),h&&f.data("bs.carousel").to(h),c.preventDefault()}}),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var c=a(this);b.call(c,c.data())})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.collapse"),f=a.extend({},c.DEFAULTS,d.data(),"object"==typeof b&&b);!e&&f.toggle&&"show"==b&&(b=!b),e||d.data("bs.collapse",e=new c(this,f)),"string"==typeof b&&e[b]()})}var c=function(b,d){this.$element=a(b),this.options=a.extend({},c.DEFAULTS,d),this.transitioning=null,this.options.parent&&(this.$parent=a(this.options.parent)),this.options.toggle&&this.toggle()};c.VERSION="3.2.0",c.DEFAULTS={toggle:!0},c.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},c.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var c=a.Event("show.bs.collapse");if(this.$element.trigger(c),!c.isDefaultPrevented()){var d=this.$parent&&this.$parent.find("> .panel > .in");if(d&&d.length){var e=d.data("bs.collapse");if(e&&e.transitioning)return;b.call(d,"hide"),e||d.data("bs.collapse",null)}var f=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[f](0),this.transitioning=1;var g=function(){this.$element.removeClass("collapsing").addClass("collapse in")[f](""),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return g.call(this);var h=a.camelCase(["scroll",f].join("-"));this.$element.one("bsTransitionEnd",a.proxy(g,this)).emulateTransitionEnd(350)[f](this.$element[0][h])}}},c.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse").removeClass("in"),this.transitioning=1;var d=function(){this.transitioning=0,this.$element.trigger("hidden.bs.collapse").removeClass("collapsing").addClass("collapse")};return a.support.transition?void this.$element[c](0).one("bsTransitionEnd",a.proxy(d,this)).emulateTransitionEnd(350):d.call(this)}}},c.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()};var d=a.fn.collapse;a.fn.collapse=b,a.fn.collapse.Constructor=c,a.fn.collapse.noConflict=function(){return a.fn.collapse=d,this},a(document).on("click.bs.collapse.data-api",'[data-toggle="collapse"]',function(c){var d,e=a(this),f=e.attr("data-target")||c.preventDefault()||(d=e.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""),g=a(f),h=g.data("bs.collapse"),i=h?"toggle":e.data(),j=e.attr("data-parent"),k=j&&a(j);h&&h.transitioning||(k&&k.find('[data-toggle="collapse"][data-parent="'+j+'"]').not(e).addClass("collapsed"),e[g.hasClass("in")?"addClass":"removeClass"]("collapsed")),b.call(g,i)})}(jQuery),+function(a){"use strict";function b(b){b&&3===b.which||(a(e).remove(),a(f).each(function(){var d=c(a(this)),e={relatedTarget:this};d.hasClass("open")&&(d.trigger(b=a.Event("hide.bs.dropdown",e)),b.isDefaultPrevented()||d.removeClass("open").trigger("hidden.bs.dropdown",e))}))}function c(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#[A-Za-z]/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}function d(b){return this.each(function(){var c=a(this),d=c.data("bs.dropdown");d||c.data("bs.dropdown",d=new g(this)),"string"==typeof b&&d[b].call(c)})}var e=".dropdown-backdrop",f='[data-toggle="dropdown"]',g=function(b){a(b).on("click.bs.dropdown",this.toggle)};g.VERSION="3.2.0",g.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=c(e),g=f.hasClass("open");if(b(),!g){"ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a('').insertAfter(a(this)).on("click",b);var h={relatedTarget:this};if(f.trigger(d=a.Event("show.bs.dropdown",h)),d.isDefaultPrevented())return;e.trigger("focus"),f.toggleClass("open").trigger("shown.bs.dropdown",h)}return!1}},g.prototype.keydown=function(b){if(/(38|40|27)/.test(b.keyCode)){var d=a(this);if(b.preventDefault(),b.stopPropagation(),!d.is(".disabled, :disabled")){var e=c(d),g=e.hasClass("open");if(!g||g&&27==b.keyCode)return 27==b.which&&e.find(f).trigger("focus"),d.trigger("click");var h=" li:not(.divider):visible a",i=e.find('[role="menu"]'+h+', [role="listbox"]'+h);if(i.length){var j=i.index(i.filter(":focus"));38==b.keyCode&&j>0&&j--,40==b.keyCode&&j').appendTo(this.$body),this.$element.on("click.dismiss.bs.modal",a.proxy(function(a){a.target===a.currentTarget&&("static"==this.options.backdrop?this.$element[0].focus.call(this.$element[0]):this.hide.call(this))},this)),e&&this.$backdrop[0].offsetWidth,this.$backdrop.addClass("in"),!b)return;e?this.$backdrop.one("bsTransitionEnd",b).emulateTransitionEnd(150):b()}else if(!this.isShown&&this.$backdrop){this.$backdrop.removeClass("in");var f=function(){c.removeBackdrop(),b&&b()};a.support.transition&&this.$element.hasClass("fade")?this.$backdrop.one("bsTransitionEnd",f).emulateTransitionEnd(150):f()}else b&&b()},c.prototype.checkScrollbar=function(){document.body.clientWidth>=window.innerWidth||(this.scrollbarWidth=this.scrollbarWidth||this.measureScrollbar())},c.prototype.setScrollbar=function(){var a=parseInt(this.$body.css("padding-right")||0,10);this.scrollbarWidth&&this.$body.css("padding-right",a+this.scrollbarWidth)},c.prototype.resetScrollbar=function(){this.$body.css("padding-right","")},c.prototype.measureScrollbar=function(){var a=document.createElement("div");a.className="modal-scrollbar-measure",this.$body.append(a);var b=a.offsetWidth-a.clientWidth;return this.$body[0].removeChild(a),b};var d=a.fn.modal;a.fn.modal=b,a.fn.modal.Constructor=c,a.fn.modal.noConflict=function(){return a.fn.modal=d,this},a(document).on("click.bs.modal.data-api",'[data-toggle="modal"]',function(c){var d=a(this),e=d.attr("href"),f=a(d.attr("data-target")||e&&e.replace(/.*(?=#[^\s]+$)/,"")),g=f.data("bs.modal")?"toggle":a.extend({remote:!/#/.test(e)&&e},f.data(),d.data());d.is("a")&&c.preventDefault(),f.one("show.bs.modal",function(a){a.isDefaultPrevented()||f.one("hidden.bs.modal",function(){d.is(":visible")&&d.trigger("focus")})}),b.call(f,g,this)})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.tooltip"),f="object"==typeof b&&b;(e||"destroy"!=b)&&(e||d.data("bs.tooltip",e=new c(this,f)),"string"==typeof b&&e[b]())})}var c=function(a,b){this.type=this.options=this.enabled=this.timeout=this.hoverState=this.$element=null,this.init("tooltip",a,b)};c.VERSION="3.2.0",c.DEFAULTS={animation:!0,placement:"top",selector:!1,template:'',trigger:"hover focus",title:"",delay:0,html:!1,container:!1,viewport:{selector:"body",padding:0}},c.prototype.init=function(b,c,d){this.enabled=!0,this.type=b,this.$element=a(c),this.options=this.getOptions(d),this.$viewport=this.options.viewport&&a(this.options.viewport.selector||this.options.viewport);for(var e=this.options.trigger.split(" "),f=e.length;f--;){var g=e[f];if("click"==g)this.$element.on("click."+this.type,this.options.selector,a.proxy(this.toggle,this));else if("manual"!=g){var h="hover"==g?"mouseenter":"focusin",i="hover"==g?"mouseleave":"focusout";this.$element.on(h+"."+this.type,this.options.selector,a.proxy(this.enter,this)),this.$element.on(i+"."+this.type,this.options.selector,a.proxy(this.leave,this))}}this.options.selector?this._options=a.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.getOptions=function(b){return b=a.extend({},this.getDefaults(),this.$element.data(),b),b.delay&&"number"==typeof b.delay&&(b.delay={show:b.delay,hide:b.delay}),b},c.prototype.getDelegateOptions=function(){var b={},c=this.getDefaults();return this._options&&a.each(this._options,function(a,d){c[a]!=d&&(b[a]=d)}),b},c.prototype.enter=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),clearTimeout(c.timeout),c.hoverState="in",c.options.delay&&c.options.delay.show?void(c.timeout=setTimeout(function(){"in"==c.hoverState&&c.show()},c.options.delay.show)):c.show()},c.prototype.leave=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),clearTimeout(c.timeout),c.hoverState="out",c.options.delay&&c.options.delay.hide?void(c.timeout=setTimeout(function(){"out"==c.hoverState&&c.hide()},c.options.delay.hide)):c.hide()},c.prototype.show=function(){var b=a.Event("show.bs."+this.type);if(this.hasContent()&&this.enabled){this.$element.trigger(b);var c=a.contains(document.documentElement,this.$element[0]);if(b.isDefaultPrevented()||!c)return;var d=this,e=this.tip(),f=this.getUID(this.type);this.setContent(),e.attr("id",f),this.$element.attr("aria-describedby",f),this.options.animation&&e.addClass("fade");var g="function"==typeof this.options.placement?this.options.placement.call(this,e[0],this.$element[0]):this.options.placement,h=/\s?auto?\s?/i,i=h.test(g);i&&(g=g.replace(h,"")||"top"),e.detach().css({top:0,left:0,display:"block"}).addClass(g).data("bs."+this.type,this),this.options.container?e.appendTo(this.options.container):e.insertAfter(this.$element);var j=this.getPosition(),k=e[0].offsetWidth,l=e[0].offsetHeight;if(i){var m=g,n=this.$element.parent(),o=this.getPosition(n);g="bottom"==g&&j.top+j.height+l-o.scroll>o.height?"top":"top"==g&&j.top-o.scroll-l<0?"bottom":"right"==g&&j.right+k>o.width?"left":"left"==g&&j.left-kg.top+g.height&&(e.top=g.top+g.height-i)}else{var j=b.left-f,k=b.left+f+c;jg.width&&(e.left=g.left+g.width-k)}return e},c.prototype.getTitle=function(){var a,b=this.$element,c=this.options;return a=b.attr("data-original-title")||("function"==typeof c.title?c.title.call(b[0]):c.title)},c.prototype.getUID=function(a){do a+=~~(1e6*Math.random());while(document.getElementById(a));return a},c.prototype.tip=function(){return this.$tip=this.$tip||a(this.options.template)},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".tooltip-arrow")},c.prototype.validate=function(){this.$element[0].parentNode||(this.hide(),this.$element=null,this.options=null)},c.prototype.enable=function(){this.enabled=!0},c.prototype.disable=function(){this.enabled=!1},c.prototype.toggleEnabled=function(){this.enabled=!this.enabled},c.prototype.toggle=function(b){var c=this;b&&(c=a(b.currentTarget).data("bs."+this.type),c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c))),c.tip().hasClass("in")?c.leave(c):c.enter(c)},c.prototype.destroy=function(){clearTimeout(this.timeout),this.hide().$element.off("."+this.type).removeData("bs."+this.type)};var d=a.fn.tooltip;a.fn.tooltip=b,a.fn.tooltip.Constructor=c,a.fn.tooltip.noConflict=function(){return a.fn.tooltip=d,this}}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.popover"),f="object"==typeof b&&b;(e||"destroy"!=b)&&(e||d.data("bs.popover",e=new c(this,f)),"string"==typeof b&&e[b]())})}var c=function(a,b){this.init("popover",a,b)};if(!a.fn.tooltip)throw new Error("Popover requires tooltip.js");c.VERSION="3.2.0",c.DEFAULTS=a.extend({},a.fn.tooltip.Constructor.DEFAULTS,{placement:"right",trigger:"click",content:"",template:''}),c.prototype=a.extend({},a.fn.tooltip.Constructor.prototype),c.prototype.constructor=c,c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.setContent=function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.options.html?"html":"text"](b),a.find(".popover-content").empty()[this.options.html?"string"==typeof c?"html":"append":"text"](c),a.removeClass("fade top bottom left right in"),a.find(".popover-title").html()||a.find(".popover-title").hide()},c.prototype.hasContent=function(){return this.getTitle()||this.getContent()},c.prototype.getContent=function(){var a=this.$element,b=this.options;return a.attr("data-content")||("function"==typeof b.content?b.content.call(a[0]):b.content)},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".arrow")},c.prototype.tip=function(){return this.$tip||(this.$tip=a(this.options.template)),this.$tip};var d=a.fn.popover;a.fn.popover=b,a.fn.popover.Constructor=c,a.fn.popover.noConflict=function(){return a.fn.popover=d,this}}(jQuery),+function(a){"use strict";function b(c,d){var e=a.proxy(this.process,this);this.$body=a("body"),this.$scrollElement=a(a(c).is("body")?window:c),this.options=a.extend({},b.DEFAULTS,d),this.selector=(this.options.target||"")+" .nav li > a",this.offsets=[],this.targets=[],this.activeTarget=null,this.scrollHeight=0,this.$scrollElement.on("scroll.bs.scrollspy",e),this.refresh(),this.process()}function c(c){return this.each(function(){var d=a(this),e=d.data("bs.scrollspy"),f="object"==typeof c&&c;e||d.data("bs.scrollspy",e=new b(this,f)),"string"==typeof c&&e[c]()})}b.VERSION="3.2.0",b.DEFAULTS={offset:10},b.prototype.getScrollHeight=function(){return this.$scrollElement[0].scrollHeight||Math.max(this.$body[0].scrollHeight,document.documentElement.scrollHeight)},b.prototype.refresh=function(){var b="offset",c=0;a.isWindow(this.$scrollElement[0])||(b="position",c=this.$scrollElement.scrollTop()),this.offsets=[],this.targets=[],this.scrollHeight=this.getScrollHeight();var d=this;this.$body.find(this.selector).map(function(){var d=a(this),e=d.data("target")||d.attr("href"),f=/^#./.test(e)&&a(e);return f&&f.length&&f.is(":visible")&&[[f[b]().top+c,e]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){d.offsets.push(this[0]),d.targets.push(this[1])})},b.prototype.process=function(){var a,b=this.$scrollElement.scrollTop()+this.options.offset,c=this.getScrollHeight(),d=this.options.offset+c-this.$scrollElement.height(),e=this.offsets,f=this.targets,g=this.activeTarget;if(this.scrollHeight!=c&&this.refresh(),b>=d)return g!=(a=f[f.length-1])&&this.activate(a);if(g&&b<=e[0])return g!=(a=f[0])&&this.activate(a);for(a=e.length;a--;)g!=f[a]&&b>=e[a]&&(!e[a+1]||b<=e[a+1])&&this.activate(f[a])},b.prototype.activate=function(b){this.activeTarget=b,a(this.selector).parentsUntil(this.options.target,".active").removeClass("active");var c=this.selector+'[data-target="'+b+'"],'+this.selector+'[href="'+b+'"]',d=a(c).parents("li").addClass("active");d.parent(".dropdown-menu").length&&(d=d.closest("li.dropdown").addClass("active")),d.trigger("activate.bs.scrollspy")};var d=a.fn.scrollspy;a.fn.scrollspy=c,a.fn.scrollspy.Constructor=b,a.fn.scrollspy.noConflict=function(){return a.fn.scrollspy=d,this},a(window).on("load.bs.scrollspy.data-api",function(){a('[data-spy="scroll"]').each(function(){var b=a(this);c.call(b,b.data())})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.tab");e||d.data("bs.tab",e=new c(this)),"string"==typeof b&&e[b]()})}var c=function(b){this.element=a(b)};c.VERSION="3.2.0",c.prototype.show=function(){var b=this.element,c=b.closest("ul:not(.dropdown-menu)"),d=b.data("target");if(d||(d=b.attr("href"),d=d&&d.replace(/.*(?=#[^\s]*$)/,"")),!b.parent("li").hasClass("active")){var e=c.find(".active:last a")[0],f=a.Event("show.bs.tab",{relatedTarget:e});if(b.trigger(f),!f.isDefaultPrevented()){var g=a(d);this.activate(b.closest("li"),c),this.activate(g,g.parent(),function(){b.trigger({type:"shown.bs.tab",relatedTarget:e})})}}},c.prototype.activate=function(b,c,d){function e(){f.removeClass("active").find("> .dropdown-menu > .active").removeClass("active"),b.addClass("active"),g?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu")&&b.closest("li.dropdown").addClass("active"),d&&d()}var f=c.find("> .active"),g=d&&a.support.transition&&f.hasClass("fade");g?f.one("bsTransitionEnd",e).emulateTransitionEnd(150):e(),f.removeClass("in")};var d=a.fn.tab;a.fn.tab=b,a.fn.tab.Constructor=c,a.fn.tab.noConflict=function(){return a.fn.tab=d,this},a(document).on("click.bs.tab.data-api",'[data-toggle="tab"], [data-toggle="pill"]',function(c){c.preventDefault(),b.call(a(this),"show")})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.affix"),f="object"==typeof b&&b;e||d.data("bs.affix",e=new c(this,f)),"string"==typeof b&&e[b]()})}var c=function(b,d){this.options=a.extend({},c.DEFAULTS,d),this.$target=a(this.options.target).on("scroll.bs.affix.data-api",a.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",a.proxy(this.checkPositionWithEventLoop,this)),this.$element=a(b),this.affixed=this.unpin=this.pinnedOffset=null,this.checkPosition()};c.VERSION="3.2.0",c.RESET="affix affix-top affix-bottom",c.DEFAULTS={offset:0,target:window},c.prototype.getPinnedOffset=function(){if(this.pinnedOffset)return this.pinnedOffset;this.$element.removeClass(c.RESET).addClass("affix");var a=this.$target.scrollTop(),b=this.$element.offset();return this.pinnedOffset=b.top-a},c.prototype.checkPositionWithEventLoop=function(){setTimeout(a.proxy(this.checkPosition,this),1)},c.prototype.checkPosition=function(){if(this.$element.is(":visible")){var b=a(document).height(),d=this.$target.scrollTop(),e=this.$element.offset(),f=this.options.offset,g=f.top,h=f.bottom;"object"!=typeof f&&(h=g=f),"function"==typeof g&&(g=f.top(this.$element)),"function"==typeof h&&(h=f.bottom(this.$element));var i=null!=this.unpin&&d+this.unpin<=e.top?!1:null!=h&&e.top+this.$element.height()>=b-h?"bottom":null!=g&&g>=d?"top":!1;if(this.affixed!==i){null!=this.unpin&&this.$element.css("top","");var j="affix"+(i?"-"+i:""),k=a.Event(j+".bs.affix");this.$element.trigger(k),k.isDefaultPrevented()||(this.affixed=i,this.unpin="bottom"==i?this.getPinnedOffset():null,this.$element.removeClass(c.RESET).addClass(j).trigger(a.Event(j.replace("affix","affixed"))),"bottom"==i&&this.$element.offset({top:b-this.$element.height()-h}))}}};var d=a.fn.affix;a.fn.affix=b,a.fn.affix.Constructor=c,a.fn.affix.noConflict=function(){return a.fn.affix=d,this},a(window).on("load",function(){a('[data-spy="affix"]').each(function(){var c=a(this),d=c.data();d.offset=d.offset||{},d.offsetBottom&&(d.offset.bottom=d.offsetBottom),d.offsetTop&&(d.offset.top=d.offsetTop),b.call(c,d)})})}(jQuery);
--------------------------------------------------------------------------------
/vendor/assets/javascripts/metricsgraphics.min.js:
--------------------------------------------------------------------------------
1 | 'use strict';var charts={};var globals={};globals.link=false;globals.version="1.0";function data_graphic(){var moz={};moz.defaults={};moz.defaults.all={missing_is_zero:false,legend:'',legend_target:'',error:'',animate_on_load:false,top:40,bottom:30,right:10,left:50,buffer:8,width:350,height:220,small_height_threshold:120,small_width_threshold:160,small_text:false,xax_count:6,xax_tick_length:5,yax_count:5,yax_tick_length:5,x_extended_ticks:false,y_extended_ticks:false,y_scale_type:'linear',max_x:null,max_y:null,min_x:null,min_y:null,point_size:2.5,x_accessor:'date',xax_units:'',x_label:'',x_axis:true,y_axis:true,y_accessor:'value',y_label:'',yax_units:'',transition_on_update:true,rollover_callback:null,show_rollover_text:true,show_confidence_band:null,xax_format:function(d){var df=d3.time.format('%b %d');var pf=d3.formatPrefix(d);switch($.type(args.data[0][0][args.x_accessor])){case'date':return df(d);break;case'number':return pf.scale(d)+pf.symbol;break;default:return d;}},area:true,chart_type:'line',data:[],decimals:2,format:'count',inflator:10/9,linked:false,list:false,baselines:null,markers:null,scalefns:{},scales:{},show_years:true,target:'#viz',interpolate:'cardinal',custom_line_color_map:[],max_data_size:null}
2 | moz.defaults.point={buffer:16,ls:false,lowess:false,point_size:2.5,size_accessor:null,color_accessor:null,size_range:null,color_range:null,size_domain:null,color_domain:null,color_type:'number'}
3 | moz.defaults.histogram={rollover_callback:function(d,i){$('#histogram svg .active_datapoint').html('Frequency Count: '+d.y);},binned:false,bins:null,processed_x_accessor:'x',processed_y_accessor:'y',processed_dx_accessor:'dx',bar_margin:1}
4 | moz.defaults.bar={y_accessor:'factor',x_accessor:'value',baseline_accessor:null,predictor_accessor:null,predictor_proportion:5,dodge_acessor:null,binned:true,padding_percentage:.1,outer_padding_percentage:.1,height:500,top:20,bar_height:20,left:70}
5 | moz.defaults.missing={top:0,bottom:0,right:0,left:0,width:350,height:220}
6 | var args=arguments[0];if(!args){args={};}
7 | var g='';if(args.list){args.x_accessor=0;args.y_accessor=1;}
8 | if(args.chart_type=='missing-data'){args=merge_with_defaults(args,moz.defaults.missing);charts.missing(args);}
9 | else if(args.chart_type=='point'){var a=merge_with_defaults(moz.defaults.point,moz.defaults.all);args=merge_with_defaults(args,a);charts.point(args).mainPlot().markers().rollover();}
10 | else if(args.chart_type=='histogram'){var a=merge_with_defaults(moz.defaults.histogram,moz.defaults.all);args=merge_with_defaults(args,a);charts.histogram(args).mainPlot().markers().rollover();}
11 | else if(args.chart_type=='bar'){var a=merge_with_defaults(moz.defaults.bar,moz.defaults.all);args=merge_with_defaults(args,a);charts.bar(args).mainPlot().markers().rollover();}
12 | else{args=merge_with_defaults(args,moz.defaults.all);charts.line(args).markers().mainPlot().rollover();}
13 | return args.data;}
14 | function chart_title(args){if(args.title&&args.title!==$(args.target+' h2.chart_title').text())
15 | $(args.target+' h2.chart_title').remove();else
16 | return;if(args.target&&args.title){var optional_question_mark=(args.description)?'':'';$(args.target).prepend(''
17 | +args.title+optional_question_mark+'
');if(args.description){var $elem=$(this);$(args.target+' h2.chart_title').popover({html:true,'animation':false,'content':args.description,'trigger':'hover','placement':'top','container':$(args.target+' h2.chart_title')});}}
18 | if(args.error){error(args);}}
19 | function y_axis(args){var svg=d3.select(args.target+' svg');var g;var min_y,max_y;args.scalefns.yf=function(di){return args.scales.Y(di[args.y_accessor]);}
20 | var min_y,max_y;var _set=false;for(var i=0;i0;});}
21 | if(a.length>0){var extent=d3.extent(a,function(d){return d[args.y_accessor];});if(!_set){min_y=extent[0];max_y=extent[1];_set=true;}else{min_y=Math.min(extent[0],min_y);max_y=Math.max(extent[1],max_y);}}}
22 | min_y=args.min_y?args.min_y:min_y;max_y=args.max_y?args.max_y:max_y;if(args.y_scale_type!='log'){if(min_y>=0){min_y=0;args.y_axis_negative=false;}else{min_y=min_y-(max_y*(args.inflator-1));args.y_axis_negative=true;}}
23 | if(args.y_scale_type=='log'){if(args.chart_type=='histogram'){min_y=0.2;}else{if(min_y<=0){min_y=1;}}
24 | args.scales.Y=d3.scale.log().domain([min_y,max_y*args.inflator]).range([args.height-args.bottom-args.buffer,args.top]).clamp(true);}else{args.scales.Y=d3.scale.linear().domain([min_y,max_y*args.inflator]).range([args.height-args.bottom-args.buffer,args.top]);}
25 | args.scales.Y_axis=d3.scale.linear().domain([min_y,max_y*args.inflator]).range([args.height-args.bottom-args.buffer,args.top]);var yax_format;if(args.format=='count'){yax_format=function(f){if(f<1.0){return args.yax_units+d3.round(f,args.decimals);}else{var pf=d3.formatPrefix(f);return args.yax_units+pf.scale(f)+pf.symbol;}};}
26 | else{yax_format=function(d_){var n=d3.format('%p');return n(d_);}}
27 | if($(args.target+' svg .y-axis').length>0){$(args.target+' svg .y-axis').remove();}
28 | if(!args.y_axis)return this;g=svg.append('g').classed('y-axis',true).classed('y-axis-small',args.use_small_class);if(args.y_label){g.append('text').attr('class','label').attr('x',function(){return-1*(args.top+args.buffer+
29 | ((args.height-args.bottom-args.buffer)
30 | -(args.top+args.buffer))/2);}).attr('y',function(){return args.left/2;}).attr("dy","0.4em").attr('text-anchor','middle').text(function(d){return args.y_label;}).attr("transform",function(d){return"rotate(-90)";});}
31 | var scale_ticks=args.scales.Y.ticks(args.yax_count);function log10(val){if(val==1000){return 3;}
32 | if(val==1000000){return 7;}
33 | return Math.log(val)/Math.LN10;}
34 | if(args.y_scale_type=='log'){scale_ticks=scale_ticks.filter(function(d){return Math.abs(log10(d))%1<1e-6||Math.abs(log10(d))%1>1-1e-6;});}
35 | var number_of_ticks=args.scales.Y.ticks(args.yax_count).length;var data_is_int=true;$.each(args.data,function(i,d){$.each(d,function(i,d){if(d[args.y_accessor]%1!==0){data_is_int=false;return false;}});});if(data_is_int&&number_of_ticks>max_y&&args.format=='count'){scale_ticks=scale_ticks.filter(function(d){return d%1===0;});}
36 | var last_i=scale_ticks.length-1;if(!args.x_extended_ticks&&!args.y_extended_ticks){g.append('line').attr('x1',args.left).attr('x2',args.left).attr('y1',args.scales.Y(scale_ticks[0])).attr('y2',args.scales.Y(scale_ticks[last_i]));}
37 | g.selectAll('.yax-ticks').data(scale_ticks).enter().append('line').classed('extended-y-ticks',args.y_extended_ticks).attr('x1',args.left).attr('x2',function(){return(args.y_extended_ticks)?args.width-args.right:args.left-args.yax_tick_length;}).attr('y1',args.scales.Y).attr('y2',args.scales.Y);g.selectAll('.yax-labels').data(scale_ticks).enter().append('text').attr('x',args.left-args.yax_tick_length*3/2).attr('dx',-3).attr('y',args.scales.Y).attr('dy','.35em').attr('text-anchor','end').text(function(d,i){var o=yax_format(d);return o;})
38 | return this;}
39 | function y_axis_categorical(args){var svg_height=args.height;if(args.chart_type=='bar'&&svg_height==null){}
40 | args.scales.Y=d3.scale.ordinal().domain(args.categorical_variables).rangeRoundBands([args.height-args.bottom-args.buffer,args.top],args.padding_percentage,args.outer_padding_percentage);args.scalefns.yf=function(di){return args.scales.Y(di[args.y_accessor]);}
41 | var svg=d3.select(args.target+' svg');var g=svg.append('g').classed('y-axis',true).classed('y-axis-small',args.use_small_class);if(!args.y_axis)return this;g.selectAll('text').data(args.categorical_variables).enter().append('svg:text').attr('x',args.left).attr('y',function(d){return args.scales.Y(d)+args.scales.Y.rangeBand()/2+(args.buffer)*args.outer_padding_percentage}).attr('dy','.35em').attr('text-anchor','end').text(String)
42 | return this;}
43 | function x_axis(args){var svg=d3.select(args.target+' svg');var g;var min_x;var max_x;args.scalefns.xf=function(di){return args.scales.X(di[args.x_accessor]);}
44 | if(args.chart_type=='point'){var min_size,max_size,min_color,max_color,size_range,color_range,size_domain,color_domain;if(args.color_accessor!=null){if(args.color_domain==null){if(args.color_type=='number'){min_color=d3.min(args.data[0],function(d){return d[args.color_accessor]});max_color=d3.max(args.data[0],function(d){return d[args.color_accessor]});color_domain=[min_color,max_color];}else if(args.color_type=='category'){color_domain=d3.set(args.data[0].map(function(d){return d[args.color_accessor];})).values();color_domain.sort();}}else{color_domain=args.color_domain;}
45 | if(args.color_range==null){if(args.color_type=='number'){color_range=['blue','red'];}else{color_range=null;}}else{color_range=args.color_range;}
46 | if(args.color_type=='number'){args.scales.color=d3.scale.linear().domain(color_domain).range(color_range).clamp(true);}else{args.scales.color=args.color_range!=null?d3.scale.ordinal().range(color_range):(color_domain.length>10?d3.scale.category20():d3.scale.category10());args.scales.color.domain(color_domain);}
47 | args.scalefns.color=function(di){return args.scales.color(di[args.color_accessor]);};}
48 | if(args.size_accessor!=null){if(args.size_domain==null){min_size=d3.min(args.data[0],function(d){return d[args.size_accessor]});max_size=d3.max(args.data[0],function(d){return d[args.size_accessor]});size_domain=[min_size,max_size];}else{size_domain=args.size_domain;}
49 | if(args.size_range==null){size_range=[1,5];}else{size_range=args.size_range;}
50 | args.scales.size=d3.scale.linear().domain(size_domain).range(size_range).clamp(true);args.scalefns.size=function(di){return args.scales.size(di[args.size_accessor]);};}}
51 | var last_i;if(args.chart_type=='line'){for(var i=0;imax_x||!max_x)
53 | max_x=args.data[i][last_i][args.x_accessor];}}
54 | else if(args.chart_type=='point'){max_x=d3.max(args.data[0],function(d){return d[args.x_accessor]});min_x=d3.min(args.data[0],function(d){return d[args.x_accessor]});}
55 | else if(args.chart_type=='histogram'){min_x=d3.min(args.data[0],function(d){return d[args.x_accessor]});max_x=d3.max(args.data[0],function(d){return d[args.x_accessor]});args.xax_format=function(f){if(f<1.0){return args.yax_units+d3.round(f,args.decimals);}
56 | else{var pf=d3.formatPrefix(f);return args.xax_units+pf.scale(f)+pf.symbol;}}}
57 | else if(args.chart_type=='bar'){min_x=0;max_x=d3.max(args.data[0],function(d){var trio=[];trio.push(d[args.x_accessor]);if(args.baseline_accessor!=null){trio.push(d[args.baseline_accessor]);};if(args.predictor_accessor!=null){trio.push(d[args.predictor_accessor]);}
58 | return Math.max.apply(null,trio);});args.xax_format=function(f){if(f<1.0){return args.yax_units+d3.round(f,args.decimals);}
59 | else{var pf=d3.formatPrefix(f);return args.xax_units+pf.scale(f)+pf.symbol;}}}
60 | min_x=args.min_x?args.min_x:min_x;max_x=args.max_x?args.max_x:max_x;args.x_axis_negative=false;if(!args.time_series){if(min_x<0){min_x=min_x-(max_x*(args.inflator-1));args.x_axis_negative=true;}}
61 | var additional_buffer;if(args.chart_type=='bar'){additional_buffer=args.buffer*5;}else{additional_buffer=0;}
62 | args.scales.X=(args.time_series)?d3.time.scale():d3.scale.linear();args.scales.X.domain([min_x,max_x]).range([args.left+args.buffer,args.width-args.right-args.buffer-additional_buffer]);if($(args.target+' svg .x-axis').length>0){$(args.target+' svg .x-axis').remove();}
63 | if(!args.x_axis)return this;g=svg.append('g').classed('x-axis',true).classed('x-axis-small',args.use_small_class);var last_i=args.scales.X.ticks(args.xax_count).length-1;if(args.x_label){g.append('text').attr('class','label').attr('x',function(){return args.left+args.buffer
64 | +((args.width-args.right-args.buffer)
65 | -(args.left+args.buffer))/2;}).attr('y',args.height-args.bottom/2).attr('dy','.50em').attr('text-anchor','middle').text(function(d){return args.x_label;})}
66 | if(args.chart_type!='bar'&&!args.x_extended_ticks&&!args.y_extended_ticks){g.append('line').attr('x1',(args.concise==false||args.xax_count==0)?args.left+args.buffer:args.scales.X(args.scales.X.ticks(args.xax_count)[0])).attr('x2',(args.concise==false||args.xax_count==0)?args.width-args.right-args.buffer:args.scales.X(args.scales.X.ticks(args.xax_count)[last_i])).attr('y1',args.height-args.bottom).attr('y2',args.height-args.bottom);}
67 | g.selectAll('.xax-ticks').data(args.scales.X.ticks(args.xax_count)).enter().append('line').attr('x1',args.scales.X).attr('x2',args.scales.X).attr('y1',args.height-args.bottom).attr('y2',function(){return(args.x_extended_ticks)?args.top:args.height-args.bottom+args.xax_tick_length;}).attr('class',function(){if(args.x_extended_ticks)
68 | return'extended-x-ticks';});g.selectAll('.xax-labels').data(args.scales.X.ticks(args.xax_count)).enter().append('text').attr('x',args.scales.X).attr('y',args.height-args.bottom+args.xax_tick_length*7/3).attr('dy','.50em').attr('text-anchor','middle').text(function(d){return args.xax_units+args.xax_format(d);})
69 | if(args.time_series&&args.show_years){var min_x;var max_x;for(var i=0;imax_x||!max_x)
71 | max_x=args.data[i][last_i][args.x_accessor];}
72 | var years=d3.time.years(min_x,max_x);if(years.length==0){var first_tick=args.scales.X.ticks(args.xax_count)[0];years=[first_tick];}
73 | g=g.append('g').classed('year-marker',true).classed('year-marker-small',args.use_small_class);g.selectAll('.year_marker').data(years).enter().append('line').attr('x1',args.scales.X).attr('x2',args.scales.X).attr('y1',args.top).attr('y2',args.height-args.bottom);var yformat=d3.time.format('%Y');g.selectAll('.year_marker').data(years).enter().append('text').attr('x',args.scales.X).attr('y',args.height-args.buffer+args.xax_tick_length).attr('dy',args.use_small_class?-3:0).attr('text-anchor','middle').text(function(d){return yformat(d);});};return this;}
74 | function init(args){var defaults={target:null,title:null,description:null};var args=arguments[0];if(!args){args={};}
75 | args=merge_with_defaults(args,defaults);if($.type(args.data[0][0][args.x_accessor])=='date'){args.time_series=true;}
76 | else{args.time_series=false;}
77 | var linked;var svg_width=args.width;var svg_height=args.height;if(args.chart_type=='bar'&&svg_height==null){svg_height=args.height=args.data[0].length*args.bar_height+args.top+args.bottom;}
78 | if(($(args.target+' svg .main-line').length>0&&args.chart_type!='line')||($(args.target+' svg .points').length>0&&args.chart_type!='point')||($(args.target+' svg .histogram').length>0&&args.chart_type!='histogram')){$(args.target).empty();}
79 | if($(args.target).is(':empty')){d3.select(args.target).append('svg').classed('linked',args.linked).attr('width',svg_width).attr('height',svg_height);}
80 | var svg=d3.select(args.target).selectAll('svg');if(args.width!=Number(svg.attr('width')))
81 | svg.attr('width',args.width)
82 | if(args.height!=Number(svg.attr('height')))
83 | svg.attr('height',args.height)
84 | svg.classed('missing',false);svg.selectAll('.missing-text').remove();chart_title(args);args.use_small_class=args.height-args.top-args.bottom-args.buffer<=args.small_height_threshold&&args.width-args.left-args.right-args.buffer*2<=args.small_width_threshold||args.small_text;if(args.data.length<$(args.target+' svg .main-line').length){if(args.custom_line_color_map.length>0){var array_full_series=function(len){var arr=new Array(len);for(var i=0;inum_of_new;i--){$(args.target+' svg .main-line.line'+i+'-color').remove();}}}
88 | return this;}
89 | function markers(args){var svg=d3.select(args.target+' svg');var gm;var gb;if(args.markers){if($(args.target+' svg .markers').length>0){$(args.target+' svg .markers').remove();}
90 | gm=svg.append('g').attr('class','markers');gm.selectAll('.markers').data(args.markers.filter(function(d){return(args.scales.X(d[args.x_accessor])>args.buffer+args.left)&&(args.scales.X(d[args.x_accessor])args.buffer+args.left)&&(args.scales.X(d[args.x_accessor])1){this.public_name[feature]=arguments[1];}
96 | if(arguments.length>2){this.sorters[feature]=arguments[2];}
97 | this.feature_set[feature]=[];return this;}
98 | this.callback=function(callback){this._callback=callback;return this;}
99 | this.display=function(){var callback=this._callback;var manual_callback=this.manual_callback;var manual_map=this.manual_map;var d,f,features,feat;features=Object.keys(this.feature_set);for(var i=0;i");var the_string='';for(var feature in this.feature_set){features=this.feature_set[feature];$(this.target+' div.segments').append(''+''+''
104 | +'
');for(var i=0;i'
105 | +features[i]+'');}}
106 | $('.'+strip_punctuation(feature)+'-btns .dropdown-menu li a').on('click',function(){var k=$(this).data('key');var feature=$(this).data('feature');var manual_feature;$('.'+strip_punctuation(feature)+'-btns button.btn span.title').html(k);if(!manual_map.hasOwnProperty(feature)){callback(feature,k);}else{manual_feature=manual_map[feature];manual_callback[manual_feature](k);}
107 | return false;})}
108 | return this;}
109 | return this}
110 | charts.line=function(args){this.args=args;this.init=function(args){raw_data_transformation(args);process_line(args);init(args);x_axis(args);y_axis(args);return this;}
111 | this.mainPlot=function(){var svg=d3.select(args.target+' svg');var g;var data_median=0;var area=d3.svg.area().x(args.scalefns.xf).y0(args.scales.Y.range()[0]).y1(args.scalefns.yf).interpolate(args.interpolate);var confidence_area;if(args.show_confidence_band){var confidence_area=d3.svg.area().x(args.scalefns.xf).y0(function(d){var l=args.show_confidence_band[0];return args.scales.Y(d[l]);}).y1(function(d){var u=args.show_confidence_band[1];return args.scales.Y(d[u]);}).interpolate(args.interpolate);}
112 | var line=d3.svg.line().x(args.scalefns.xf).y(args.scalefns.yf).interpolate(args.interpolate);var flat_line=d3.svg.line().x(args.scalefns.xf).y(function(){return args.scales.Y(data_median);}).interpolate(args.interpolate);var legend='';var this_data;for(var i=args.data.length-1;i>=0;i--){this_data=args.data[i];var line_id=i+1;if(args.custom_line_color_map.length>0){line_id=args.custom_line_color_map[i];}
113 | if(args.show_confidence_band){svg.append('path').attr('class','confidence-band').attr('d',confidence_area(args.data[i]));}
114 | if(args.area&&!args.y_axis_negative&&args.data.length<=1){if($(args.target+' svg path.area'+(line_id)+'-color').length>0){d3.selectAll(args.target+' svg path.area'+(line_id)+'-color').transition().duration(function(){return(args.transition_on_update)?1000:0;}).attr('d',area(args.data[i]));}
115 | else{svg.append('path').attr('class','main-area '+'area'+(line_id)+'-color').attr('d',area(args.data[i]));}}
116 | if($(args.target+' svg path.line'+(line_id)+'-color').length>0){d3.selectAll(args.target+' svg path.line'+(line_id)+'-color').transition().duration(function(){return(args.transition_on_update)?1000:0;}).attr('d',line(args.data[i]));}
117 | else{if(args.animate_on_load){data_median=d3.median(args.data[i],function(d){return d[args.y_accessor];})
118 | svg.append('path').attr('class','main-line '+'line'+(line_id)+'-color').attr('d',flat_line(args.data[i])).transition().duration(1000).attr('d',line(args.data[i]));}
119 | else{svg.append('path').attr('class','main-line '+'line'+(line_id)+'-color').attr('d',line(args.data[i]));}}
120 | if(args.legend){legend+="— "+args.legend[i]+" ";}}
121 | if(args.legend){$(args.legend_target).html(legend);}
122 | return this;}
123 | this.markers=function(){markers(args);return this;};this.rollover=function(){var svg=d3.select(args.target+' svg');var g;if($(args.target+' svg .transparent-rollover-rect').length>0){$(args.target+' svg .transparent-rollover-rect').remove();}
124 | if($(args.target+' svg .voronoi').length>0){$(args.target+' svg .voronoi').remove();}
125 | if($(args.target+' svg .active_datapoint').length>0){$(args.target+' svg .active_datapoint').remove();}
126 | if($(args.target+' svg .line_rollover_circle').length>0){$(args.target+' svg .line_rollover_circle').remove();}
127 | svg.append('text').attr('class','active_datapoint').classed('active-datapoint-small',args.use_small_class).attr('xml:space','preserve').attr('x',args.width-args.right).attr('y',args.top/2).attr('text-anchor','end');svg.append('circle').classed('line_rollover_circle',true).attr('cx',0).attr('cy',0).attr('r',0);var line_id=1;for(var i=0;i0){args.data[i][j]['line_id']=args.custom_line_color_map[i];}
128 | else{args.data[i][j]['line_id']=line_id;}}
129 | line_id++;}
130 | if(args.data.length>1){var voronoi=d3.geom.voronoi().x(function(d){return args.scales.X(d[args.x_accessor]);}).y(function(d){return args.scales.Y(d[args.y_accessor]);}).clipExtent([[args.buffer,args.buffer],[args.width-args.buffer,args.height-args.buffer]]);var g=svg.append('g').attr('class','voronoi')
131 | var data_nested=d3.nest().key(function(d){return args.scales.X(d[args.x_accessor])+","+args.scales.Y(d[args.y_accessor]);}).rollup(function(v){return v[0];}).entries(d3.merge(args.data.map(function(d){return d;}))).map(function(d){return d.values;});g.selectAll('path').data(voronoi(data_nested)).enter().append('path').attr("d",function(d){return"M"+d.join("L")+"Z";}).datum(function(d){return d.point;}).attr('class',function(d){if(args.linked){var v=d[args.x_accessor];var formatter=d3.time.format('%Y-%m-%d');return'line'+d['line_id']+'-color '+'roll_'+formatter(v);}
132 | else{return'line'+d['line_id']+'-color';}}).on('mouseover',this.rolloverOn(args)).on('mouseout',this.rolloverOff(args));}
133 | else{var line_id=1;if(args.custom_line_color_map.length>0){line_id=args.custom_line_color_map[0];}
134 | var g=svg.append('g').attr('class','transparent-rollover-rect')
135 | var xf=args.data[0].map(args.scalefns.xf);g.selectAll('.rollover-rects').data(args.data[0]).enter().append('rect').attr('class',function(d,i){if(args.linked){var v=d[args.x_accessor];var formatter=d3.time.format('%Y-%m-%d');var id=(typeof v==='number')?i:formatter(v);return'line'+line_id+'-color '+'roll_'+id;}
136 | else{return'line'+line_id+'-color';}}).attr('x',function(d,i){if(i==0){return xf[i];}else{return(xf[i-1]+xf[i])/2;}}).attr('y',function(d,i){return(args.data.length>1)?args.scalefns.yf(d)-6:args.top;}).attr('width',function(d,i){if(i==0){return(xf[i+1]-xf[i])/2;}else if(i==xf.length-1){return(xf[i]-xf[i-1])/2;}else{return(xf[i+1]-xf[i-1])/2;}}).attr('height',function(d,i){return(args.data.length>1)?12:args.height-args.bottom-args.top-args.buffer;}).attr('opacity',0).on('mouseover',this.rolloverOn(args)).on('mouseout',this.rolloverOff(args));}
137 | return this;}
138 | this.rolloverOn=function(args){var svg=d3.select(args.target+' svg');var x_formatter=d3.time.format('%Y-%m-%d');return function(d,i){svg.selectAll('circle.line_rollover_circle').attr('class',"").attr('class','area'+d['line_id']+'-color').classed('line_rollover_circle',true).attr('cx',function(){return args.scales.X(d[args.x_accessor]);}).attr('cy',function(){return args.scales.Y(d[args.y_accessor]);}).attr('r',args.point_size).style('opacity',1);if(args.linked&&!globals.link){globals.link=true;var v=d[args.x_accessor];var formatter=d3.time.format('%Y-%m-%d');var id=(typeof v==='number')?i:formatter(v);d3.selectAll('.line'+d['line_id']+'-color.roll_'+id).each(function(d,i){d3.select(this).on('mouseover')(d,i);})}
139 | svg.selectAll('text').filter(function(g,j){return d==g;}).attr('opacity',0.3);var fmt=d3.time.format('%b %e, %Y');if(args.format=='count'){var num=function(d_){var is_float=d_%1!=0;var n=d3.format("0,000");d_=is_float?d3.round(d_,args.decimals):d_;return n(d_);}}
140 | else{var num=function(d_){var fmt_string=(args.decimals?'.'+args.decimals:'')+'%';var n=d3.format(fmt_string);return n(d_);}}
141 | if(args.show_rollover_text){svg.select('.active_datapoint').text(function(){if(args.time_series){var dd=new Date(+d[args.x_accessor]);dd.setDate(dd.getDate());return fmt(dd)+' '+args.yax_units
142 | +num(d[args.y_accessor]);}
143 | else{return args.x_accessor+': '+d[args.x_accessor]
144 | +', '+args.y_accessor+': '+args.yax_units
145 | +num(d[args.y_accessor]);}});}
146 | if(args.rollover_callback){args.rollover_callback(d,i);}}}
147 | this.rolloverOff=function(args){var svg=d3.select(args.target+' svg');return function(d,i){if(args.linked&&globals.link){globals.link=false;var v=d[args.x_accessor];var formatter=d3.time.format('%Y-%m-%d');var id=(typeof v==='number')?i:formatter(v);d3.selectAll('.roll_'+id).each(function(d,i){d3.select(this).on('mouseout')(d);});}
148 | svg.selectAll('circle.line_rollover_circle').style('opacity',0);svg.select('.active_datapoint').text('');}}
149 | this.init(args);return this;}
150 | charts.histogram=function(args){this.args=args;this.init=function(args){raw_data_transformation(args);process_histogram(args);init(args);x_axis(args);y_axis(args);return this;}
151 | this.mainPlot=function(){var svg=d3.select(args.target+' svg');var g;if($(args.target+' svg .histogram').length>0){$(args.target+' svg .histogram').remove();}
152 | var g=svg.append("g").attr("class","histogram");var bar=g.selectAll(".bar").data(args.data[0]).enter().append("g").attr("class","bar").attr("transform",function(d){return"translate("+args.scales.X(d[args.x_accessor])
153 | +","+args.scales.Y(d[args.y_accessor])+")";});bar.append("rect").attr("x",1).attr("width",function(d,i){return args.scalefns.xf(args.data[0][1])
154 | -args.scalefns.xf(args.data[0][0])
155 | -args.bar_margin;}).attr("height",function(d){if(d[args.y_accessor]==0)
156 | return 0;return args.height-args.bottom-args.buffer
157 | -args.scales.Y(d[args.y_accessor]);});return this;}
158 | this.markers=function(){markers(args);return this;};this.rollover=function(){var svg=d3.select(args.target+' svg');var g;if($(args.target+' svg .transparent-rollover-rect').length>0){$(args.target+' svg .transparent-rollover-rect').remove();}
159 | if($(args.target+' svg .active_datapoint').length>0){$(args.target+' svg .active_datapoint').remove();}
160 | svg.append('text').attr('class','active_datapoint').attr('xml:space','preserve').attr('x',args.width-args.right).attr('y',args.top/2).attr('text-anchor','end');var g=svg.append('g').attr('class','transparent-rollover-rect')
161 | var bar=g.selectAll(".bar").data(args.data[0]).enter().append("g").attr("class","rollover-rects").attr("transform",function(d){return"translate("+(args.scales.X(d[args.x_accessor]))+","+0+")";});bar.append("rect").attr("x",1).attr("y",0).attr("width",function(d,i){if(i!=args.data[0].length-1){return args.scalefns.xf(args.data[0][i+1])
162 | -args.scalefns.xf(d);}
163 | else{return args.scalefns.xf(args.data[0][1])
164 | -args.scalefns.xf(args.data[0][0]);}}).attr("height",function(d){return args.height;}).attr('opacity',0).on('mouseover',this.rolloverOn(args)).on('mouseout',this.rolloverOff(args));}
165 | this.rolloverOn=function(args){var svg=d3.select(args.target+' svg');var x_formatter=d3.time.format('%Y-%m-%d');return function(d,i){svg.selectAll('text').filter(function(g,j){return d==g;}).attr('opacity',0.3);var fmt=d3.time.format('%b %e, %Y');if(args.format=='count'){var num=function(d_){var is_float=d_%1!=0;var n=d3.format("0,000");d_=is_float?d3.round(d_,args.decimals):d_;return n(d_);}}
166 | else{var num=function(d_){var fmt_string=(args.decimals?'.'+args.decimals:'')+'%';var n=d3.format(fmt_string);return n(d_);}}
167 | d3.selectAll($(args.target+' svg .bar :eq('+i+')')).classed('active',true);if(args.show_rollover_text){svg.select('.active_datapoint').text(function(){if(args.time_series){var dd=new Date(+d[args.x_accessor]);dd.setDate(dd.getDate());return fmt(dd)+' '+args.yax_units
168 | +num(d[args.y_accessor]);}
169 | else{return args.x_accessor+': '+num(d[args.x_accessor])
170 | +', '+args.y_accessor+': '+args.yax_units
171 | +num(d[args.y_accessor]);}});}
172 | if(args.rollover_callback){args.rollover_callback(d,i);}}}
173 | this.rolloverOff=function(args){var svg=d3.select(args.target+' svg');return function(d,i){d3.selectAll($(args.target+' svg .bar :eq('+i+')')).classed('active',false);svg.select('.active_datapoint').text('');}}
174 | this.init(args);return this;}
175 | charts.point=function(args){this.args=args;this.init=function(args){raw_data_transformation(args);process_point(args);init(args);x_axis(args);y_axis(args);return this;}
176 | this.markers=function(){markers(args);if(args.least_squares){add_ls(args);}
177 | return this}
178 | this.mainPlot=function(){var svg=d3.select(args.target+' svg');var g;g=svg.append('g').classed('points',true);var pts=g.selectAll('circle').data(args.data[0]).enter().append('svg:circle').attr('class',function(d,i){return'path-'+i;}).attr('cx',args.scalefns.xf).attr('cy',args.scalefns.yf);if(args.color_accessor!=null){pts.attr('fill',args.scalefns.color);pts.attr('stroke',args.scalefns.color);}
179 | else{pts.classed('points-mono',true);}
180 | if(args.size_accessor!=null){pts.attr('r',args.scalefns.size);}
181 | else{pts.attr('r',args.point_size);}
182 | var rug;if(args.x_rug){rug=g.selectAll('line.x_rug').data(args.data[0]).enter().append('svg:line').attr('x1',args.scalefns.xf).attr('x2',args.scalefns.xf).attr('y1',args.height-args.top+args.buffer/2).attr('y2',args.height-args.top).attr('class','x-rug').attr('opacity',0.3);if(args.color_accessor){rug.attr('stroke',args.scalefns.color);}
183 | else{rug.classed('x-rug-mono',true);}}
184 | if(args.y_rug){rug=g.selectAll('line.y_rug').data(args.data[0]).enter().append('svg:line').attr('x1',args.left+1).attr('x2',args.left+args.buffer/2).attr('y1',args.scalefns.yf).attr('y2',args.scalefns.yf).attr('class','y-rug').attr('opacity',0.3);if(args.color_accessor){rug.attr('stroke',args.scalefns.color);}
185 | else{rug.classed('y-rug-mono',true);}}
186 | return this;}
187 | this.rollover=function(){var svg=d3.select(args.target+' svg');if($(args.target+' svg .active_datapoint').length>0){$(args.target+' svg .active_datapoint').remove();}
188 | svg.append('text').attr('class','active_datapoint').attr('xml:space','preserve').attr('x',args.width-args.right).attr('y',args.top/2).attr('text-anchor','end');var voronoi=d3.geom.voronoi().x(args.scalefns.xf).y(args.scalefns.yf).clipExtent([[args.buffer,args.buffer],[args.width-args.buffer,args.height-args.buffer]]);var paths=svg.append('g').attr('class','voronoi');paths.selectAll('path').data(voronoi(args.data[0])).enter().append('path').attr('d',function(d){if(d==undefined)return;return'M'+d.join(',')+'Z';}).attr('class',function(d,i){return'path-'+i;}).style('fill-opacity',0).on('mouseover',this.rolloverOn(args)).on('mouseout',this.rolloverOff(args));return this;}
189 | this.rolloverOn=function(args){var svg=d3.select(args.target+' svg');return function(d,i){svg.selectAll('.points circle').classed('selected',false);var pts=svg.selectAll('.points circle.path-'+i).classed('selected',true);if(args.size_accessor){pts.attr('r',function(di){return args.scalefns.size(di)+1});}else{pts.attr('r',args.point_size);}
190 | if(args.linked&&!globals.link){globals.link=true;d3.selectAll('.voronoi .path-'+i).each(function(){d3.select(this).on('mouseover')(d,i);})}
191 | var fmt=d3.time.format('%b %e, %Y');if(args.format=='count'){var num=function(d_){var is_float=d_%1!=0;var n=d3.format("0,000");d_=is_float?d3.round(d_,args.decimals):d_;return n(d_);}}
192 | else{var num=function(d_){var fmt_string=(args.decimals?'.'+args.decimals:'')+'%';var n=d3.format(fmt_string);return n(d_);}}
193 | if(args.show_rollover_text){svg.select('.active_datapoint').text(function(){if(args.time_series){var dd=new Date(+d['point'][args.x_accessor]);dd.setDate(dd.getDate());return fmt(dd)+' '+args.yax_units
194 | +num(d['point'][args.y_accessor]);}
195 | else{return args.x_accessor+': '+num(d['point'][args.x_accessor])
196 | +', '+args.y_accessor+': '+args.yax_units
197 | +num(d['point'][args.y_accessor]);}});}
198 | if(args.rollover_callback){args.rollover_callback(d,i);}}}
199 | this.rolloverOff=function(args){var svg=d3.select(args.target+' svg');return function(d,i){if(args.linked&&globals.link){globals.link=false;d3.selectAll('.voronoi .path-'+i).each(function(){d3.select(this).on('mouseout')(d,i);})}
200 | var pts=svg.selectAll('.points circle').classed('unselected',false).classed('selected',false);if(args.size_accessor){pts.attr('r',args.scalefns.size);}
201 | else{pts.attr('r',args.point_size);}
202 | svg.select('.active_datapoint').text('');}}
203 | this.update=function(args){return this;}
204 | this.init(args);return this;}
205 | charts.bar=function(args){this.args=args;this.init=function(args){raw_data_transformation(args);process_categorical_variables(args);init(args);x_axis(args);y_axis_categorical(args);return this;}
206 | this.mainPlot=function(){var svg=d3.select(args.target+' svg');var g;if($(args.target+' svg .barplot').length>0){$(args.target+' svg .barplot').remove();}
207 | var data=args.data[0];var g=svg.append('g').classed('barplot',true);var appropriate_height=args.scales.Y.rangeBand()/1.5;g.selectAll('.bar').data(data).enter().append('rect').classed('bar',true).attr('x',args.scales.X(0)).attr('y',function(d){return args.scalefns.yf(d)+appropriate_height/2;}).attr('height',appropriate_height).attr('width',function(d){return args.scalefns.xf(d)-args.scales.X(0)});if(args.predictor_accessor){var pp=args.predictor_proportion;var pp0=pp-1;g.selectAll('.prediction').data(data).enter().append("rect").attr('x',args.scales.X(0)).attr('y',function(d){return args.scalefns.yf(d)+pp0*appropriate_height/(pp*2)+appropriate_height/2;}).attr('height',appropriate_height/pp).attr('width',function(d){return args.scales.X(d[args.predictor_accessor])-args.scales.X(0);}).attr('fill','#36454f');}
208 | if(args.baseline_accessor){g.selectAll('.baseline').data(data).enter().append("line").attr('x1',function(d){return args.scales.X(d[args.baseline_accessor])}).attr('x2',function(d){return args.scales.X(d[args.baseline_accessor])}).attr('y1',function(d){return args.scalefns.yf(d)+appropriate_height/2-appropriate_height/pp+appropriate_height/2;}).attr('y2',function(d){return args.scalefns.yf(d)+appropriate_height/2+appropriate_height/pp+appropriate_height/2;}).attr('stroke-width',2).attr('stroke','#36454f');}
209 | return this;}
210 | this.markers=function(){markers(args);return this;};this.rollover=function(){var svg=d3.select(args.target+' svg');var g;if($(args.target+' svg .transparent-rollover-rect').length>0){$(args.target+' svg .transparent-rollover-rect').remove();}
211 | if($(args.target+' svg .active_datapoint').length>0){$(args.target+' svg .active_datapoint').remove();}
212 | svg.append('text').attr('class','active_datapoint').attr('xml:space','preserve').attr('x',args.width-args.right).attr('y',args.top/2).attr('dy','.35em').attr('text-anchor','end');var g=svg.append('g').attr('class','transparent-rollover-rect')
213 | var bar=g.selectAll(".bar").data(args.data[0]).enter().append("rect").attr("x",args.scales.X(0)).attr("y",args.scalefns.yf).attr('width',args.width).attr('height',args.scales.Y.rangeBand()+2).attr('opacity',0).on('mouseover',this.rolloverOn(args)).on('mouseout',this.rolloverOff(args));}
214 | this.rolloverOn=function(args){var svg=d3.select(args.target+' svg');var x_formatter=d3.time.format('%Y-%m-%d');return function(d,i){svg.selectAll('text').filter(function(g,j){return d==g;}).attr('opacity',0.3);var fmt=d3.time.format('%b %e, %Y');if(args.format=='count'){var num=function(d_){var is_float=d_%1!=0;var n=d3.format("0,000");d_=is_float?d3.round(d_,args.decimals):d_;return n(d_);}}
215 | else{var num=function(d_){var fmt_string=(args.decimals?'.'+args.decimals:'')+'%';var n=d3.format(fmt_string);return n(d_);}}
216 | d3.selectAll($(args.target+' svg g.barplot .bar:eq('+i+')')).classed('active',true);if(args.show_rollover_text){svg.select('.active_datapoint').text(function(){if(args.time_series){var dd=new Date(+d[args.x_accessor]);dd.setDate(dd.getDate());return fmt(dd)+' '+args.yax_units
217 | +num(d[args.y_accessor]);}
218 | else{return d[args.y_accessor]+': '+num(d[args.x_accessor]);}});}
219 | if(args.rollover_callback){args.rollover_callback(d,i);}}}
220 | this.rolloverOff=function(args){var svg=d3.select(args.target+' svg');return function(d,i){d3.selectAll($(args.target+' svg g.barplot .bar:eq('+i+')')).classed('active',false);svg.select('.active_datapoint').text('');}}
221 | this.init(args);return this;}
222 | charts.missing=function(args){this.args=args;this.init=function(args){chart_title(args);d3.select(args.target).selectAll('svg').data([args]).enter().append('svg').attr('width',args.width).attr('height',args.height);d3.select(args.target).selectAll('svg *').remove()
223 | var svg=d3.select(args.target).select('svg')
224 | svg.classed('missing',true);svg.append('rect').attr('class','missing-pane').attr('x',args.left).attr('y',args.top).attr('width',args.width-(args.left*2)).attr('height',args.height-(args.top*2));var missing_text='Data currently missing or unavailable';svg.selectAll('.missing_text').data([missing_text]).enter().append('text').attr('class','missing-text').attr('x',args.width/2).attr('y',args.height/2).attr('dy','.50em').attr('text-anchor','middle').text(missing_text)
225 | return this;}
226 | this.init(args);return this;}
227 | function raw_data_transformation(args){if(!$.isArray(args.data[0]))
228 | args.data=[args.data];if($.isArray(args.y_accessor)){args.data=args.data.map(function(_d){return args.y_accessor.map(function(ya){return _d.map(function(di){di=clone(di);if(di[ya]==undefined){return undefined;}
229 | di['multiline_y_accessor']=di[ya];return di;}).filter(function(di){return di!=undefined;})})})[0];args.y_accessor='multiline_y_accessor';}
230 | if(args.chart_type=='line'){for(var i=0;i=0&&u<=1){return Math.pow(1-Math.pow(u,w),w)}else{return 0}}
263 | function _bisquare_weight(u){return _pow_weight(u,2);}
264 | function _tricube_weight(u){return _pow_weight(u,3);}
265 | function _neighborhood_width(x0,xis){return Array.max(xis.map(function(xi){return Math.abs(x0-xi)}))}
266 | function _manhattan(x1,x2){return Math.abs(x1-x2)}
267 | function _weighted_means(wxy){var wsum=d3.sum(wxy.map(function(wxyi){return wxyi.w}));return{xbar:d3.sum(wxy.map(function(wxyi){return wxyi.w*wxyi.x}))/wsum,ybar:d3.sum(wxy.map(function(wxyi){return wxyi.w*wxyi.y}))/wsum}}
268 | function _weighted_beta(wxy,xbar,ybar){var num=d3.sum(wxy.map(function(wxyi){return Math.pow(wxyi.w,2)*(wxyi.x-xbar)*(wxyi.y-ybar)}))
269 | var denom=d3.sum(wxy.map(function(wxyi){return Math.pow(wxyi.w,2)*(Math.pow(wxyi.x-xbar),2)}))
270 | return num/denom;}
271 | function _weighted_least_squares(wxy){var ybar,xbar,beta_i,x0;var _wm=_weighted_means(wxy);xbar=_wm.xbar;ybar=_wm.ybar;var beta=_weighted_beta(wxy,xbar,ybar)
272 | return{beta:beta,xbar:xbar,ybar:ybar,x0:ybar-beta*xbar}
273 | return num/denom}
274 | function _calculate_lowess_fit(x,y,alpha,inc,residuals){var k=Math.floor(x.length*alpha);var sorted_x=x.slice();sorted_x.sort(function(a,b){if(ab){return 1}
276 | return 0});var x_max=d3.quantile(sorted_x,.98);var x_min=d3.quantile(sorted_x,.02);var xy=d3.zip(x,y,residuals).sort();var size=Math.abs(x_max-x_min)/inc;var smallest=x_min
277 | var largest=x_max
278 | var x_proto=d3.range(smallest,largest,size);var xi_neighbors;var x_i,beta_i,x0_i,delta_i,xbar,ybar;var y_proto=[];for(var i=0;i0;}
296 | function has_too_many_zeros(data,accessor,zero_count){return number_of_values(data,accessor,0)>=zero_count;}
297 | function clone(obj){if(null==obj||"object"!=typeof obj)return obj;if(obj instanceof Date){var copy=new Date();copy.setTime(obj.getTime());return copy;}
298 | if(obj instanceof Array){var copy=[];for(var i=0,len=obj.length;i