timescaledb 0.2.6 → 0.2.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (102) hide show
  1. checksums.yaml +4 -4
  2. data/lib/timescaledb/acts_as_hypertable/core.rb +1 -1
  3. data/lib/timescaledb/database/quoting.rb +12 -0
  4. data/lib/timescaledb/database/schema_statements.rb +168 -0
  5. data/lib/timescaledb/database/types.rb +17 -0
  6. data/lib/timescaledb/database.rb +11 -0
  7. data/lib/timescaledb/toolkit/time_vector.rb +41 -4
  8. data/lib/timescaledb/version.rb +1 -1
  9. metadata +6 -95
  10. data/.github/workflows/ci.yml +0 -72
  11. data/.gitignore +0 -12
  12. data/.rspec +0 -3
  13. data/.ruby-version +0 -1
  14. data/.tool-versions +0 -1
  15. data/.travis.yml +0 -9
  16. data/CODE_OF_CONDUCT.md +0 -74
  17. data/Fastfile +0 -17
  18. data/Gemfile +0 -8
  19. data/Gemfile.lock +0 -75
  20. data/Gemfile.scenic +0 -7
  21. data/Gemfile.scenic.lock +0 -119
  22. data/README.md +0 -490
  23. data/Rakefile +0 -21
  24. data/bin/console +0 -28
  25. data/bin/setup +0 -13
  26. data/docs/command_line.md +0 -178
  27. data/docs/img/lttb_example.png +0 -0
  28. data/docs/img/lttb_sql_vs_ruby.gif +0 -0
  29. data/docs/img/lttb_zoom.gif +0 -0
  30. data/docs/index.md +0 -72
  31. data/docs/migrations.md +0 -76
  32. data/docs/models.md +0 -78
  33. data/docs/toolkit.md +0 -507
  34. data/docs/toolkit_lttb_tutorial.md +0 -557
  35. data/docs/toolkit_lttb_zoom.md +0 -357
  36. data/docs/toolkit_ohlc.md +0 -315
  37. data/docs/videos.md +0 -16
  38. data/examples/all_in_one/all_in_one.rb +0 -94
  39. data/examples/all_in_one/benchmark_comparison.rb +0 -108
  40. data/examples/all_in_one/caggs.rb +0 -93
  41. data/examples/all_in_one/query_data.rb +0 -78
  42. data/examples/ranking/.gitattributes +0 -7
  43. data/examples/ranking/.gitignore +0 -29
  44. data/examples/ranking/.ruby-version +0 -1
  45. data/examples/ranking/Gemfile +0 -33
  46. data/examples/ranking/Gemfile.lock +0 -189
  47. data/examples/ranking/README.md +0 -166
  48. data/examples/ranking/Rakefile +0 -6
  49. data/examples/ranking/app/controllers/application_controller.rb +0 -2
  50. data/examples/ranking/app/controllers/concerns/.keep +0 -0
  51. data/examples/ranking/app/jobs/application_job.rb +0 -7
  52. data/examples/ranking/app/models/application_record.rb +0 -3
  53. data/examples/ranking/app/models/concerns/.keep +0 -0
  54. data/examples/ranking/app/models/game.rb +0 -2
  55. data/examples/ranking/app/models/play.rb +0 -7
  56. data/examples/ranking/bin/bundle +0 -114
  57. data/examples/ranking/bin/rails +0 -4
  58. data/examples/ranking/bin/rake +0 -4
  59. data/examples/ranking/bin/setup +0 -33
  60. data/examples/ranking/config/application.rb +0 -39
  61. data/examples/ranking/config/boot.rb +0 -4
  62. data/examples/ranking/config/credentials.yml.enc +0 -1
  63. data/examples/ranking/config/database.yml +0 -86
  64. data/examples/ranking/config/environment.rb +0 -5
  65. data/examples/ranking/config/environments/development.rb +0 -60
  66. data/examples/ranking/config/environments/production.rb +0 -75
  67. data/examples/ranking/config/environments/test.rb +0 -53
  68. data/examples/ranking/config/initializers/cors.rb +0 -16
  69. data/examples/ranking/config/initializers/filter_parameter_logging.rb +0 -8
  70. data/examples/ranking/config/initializers/inflections.rb +0 -16
  71. data/examples/ranking/config/initializers/timescale.rb +0 -2
  72. data/examples/ranking/config/locales/en.yml +0 -33
  73. data/examples/ranking/config/puma.rb +0 -43
  74. data/examples/ranking/config/routes.rb +0 -6
  75. data/examples/ranking/config/storage.yml +0 -34
  76. data/examples/ranking/config.ru +0 -6
  77. data/examples/ranking/db/migrate/20220209120747_create_games.rb +0 -10
  78. data/examples/ranking/db/migrate/20220209120910_create_plays.rb +0 -19
  79. data/examples/ranking/db/migrate/20220209143347_create_score_per_hours.rb +0 -5
  80. data/examples/ranking/db/schema.rb +0 -47
  81. data/examples/ranking/db/seeds.rb +0 -7
  82. data/examples/ranking/db/views/score_per_hours_v01.sql +0 -7
  83. data/examples/ranking/lib/tasks/.keep +0 -0
  84. data/examples/ranking/log/.keep +0 -0
  85. data/examples/ranking/public/robots.txt +0 -1
  86. data/examples/ranking/storage/.keep +0 -0
  87. data/examples/ranking/tmp/.keep +0 -0
  88. data/examples/ranking/tmp/pids/.keep +0 -0
  89. data/examples/ranking/tmp/storage/.keep +0 -0
  90. data/examples/ranking/vendor/.keep +0 -0
  91. data/examples/toolkit-demo/compare_volatility.rb +0 -104
  92. data/examples/toolkit-demo/lttb/README.md +0 -15
  93. data/examples/toolkit-demo/lttb/lttb.rb +0 -92
  94. data/examples/toolkit-demo/lttb/lttb_sinatra.rb +0 -139
  95. data/examples/toolkit-demo/lttb/lttb_test.rb +0 -21
  96. data/examples/toolkit-demo/lttb/views/index.erb +0 -27
  97. data/examples/toolkit-demo/lttb-zoom/README.md +0 -13
  98. data/examples/toolkit-demo/lttb-zoom/lttb_zoomable.rb +0 -90
  99. data/examples/toolkit-demo/lttb-zoom/views/index.erb +0 -33
  100. data/examples/toolkit-demo/ohlc.rb +0 -175
  101. data/mkdocs.yml +0 -34
  102. data/timescaledb.gemspec +0 -40
@@ -1,53 +0,0 @@
1
- require "active_support/core_ext/integer/time"
2
-
3
- # The test environment is used exclusively to run your application's
4
- # test suite. You never need to work with it otherwise. Remember that
5
- # your test database is "scratch space" for the test suite and is wiped
6
- # and recreated between test runs. Don't rely on the data there!
7
-
8
- Rails.application.configure do
9
- # Settings specified here will take precedence over those in config/application.rb.
10
-
11
- # Turn false under Spring and add config.action_view.cache_template_loading = true.
12
- config.cache_classes = true
13
-
14
- # Eager loading loads your whole application. When running a single test locally,
15
- # this probably isn't necessary. It's a good idea to do in a continuous integration
16
- # system, or in some way before deploying your code.
17
- config.eager_load = ENV["CI"].present?
18
-
19
- # Configure public file server for tests with Cache-Control for performance.
20
- config.public_file_server.enabled = true
21
- config.public_file_server.headers = {
22
- "Cache-Control" => "public, max-age=#{1.hour.to_i}"
23
- }
24
-
25
- # Show full error reports and disable caching.
26
- config.consider_all_requests_local = true
27
- config.action_controller.perform_caching = false
28
- config.cache_store = :null_store
29
-
30
- # Raise exceptions instead of rendering exception templates.
31
- config.action_dispatch.show_exceptions = false
32
-
33
- # Disable request forgery protection in test environment.
34
- config.action_controller.allow_forgery_protection = false
35
-
36
- # Store uploaded files on the local file system in a temporary directory.
37
- config.active_storage.service = :test
38
-
39
- # Print deprecation notices to the stderr.
40
- config.active_support.deprecation = :stderr
41
-
42
- # Raise exceptions for disallowed deprecations.
43
- config.active_support.disallowed_deprecation = :raise
44
-
45
- # Tell Active Support which deprecation messages to disallow.
46
- config.active_support.disallowed_deprecation_warnings = []
47
-
48
- # Raises error for missing translations.
49
- # config.i18n.raise_on_missing_translations = true
50
-
51
- # Annotate rendered view with file names.
52
- # config.action_view.annotate_rendered_view_with_filenames = true
53
- end
@@ -1,16 +0,0 @@
1
- # Be sure to restart your server when you modify this file.
2
-
3
- # Avoid CORS issues when API is called from the frontend app.
4
- # Handle Cross-Origin Resource Sharing (CORS) in order to accept cross-origin AJAX requests.
5
-
6
- # Read more: https://github.com/cyu/rack-cors
7
-
8
- # Rails.application.config.middleware.insert_before 0, Rack::Cors do
9
- # allow do
10
- # origins "example.com"
11
- #
12
- # resource "*",
13
- # headers: :any,
14
- # methods: [:get, :post, :put, :patch, :delete, :options, :head]
15
- # end
16
- # end
@@ -1,8 +0,0 @@
1
- # Be sure to restart your server when you modify this file.
2
-
3
- # Configure parameters to be filtered from the log file. Use this to limit dissemination of
4
- # sensitive information. See the ActiveSupport::ParameterFilter documentation for supported
5
- # notations and behaviors.
6
- Rails.application.config.filter_parameters += [
7
- :passw, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn
8
- ]
@@ -1,16 +0,0 @@
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
@@ -1,2 +0,0 @@
1
- require 'timescaledb'
2
- require 'scenic'
@@ -1,33 +0,0 @@
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
- # The following keys must be escaped otherwise they will not be retrieved by
20
- # the default I18n backend:
21
- #
22
- # true, false, on, off, yes, no
23
- #
24
- # Instead, surround them with single quotes.
25
- #
26
- # en:
27
- # "true": "foo"
28
- #
29
- # To learn more, please read the Rails Internationalization guide
30
- # available at https://guides.rubyonrails.org/i18n.html.
31
-
32
- en:
33
- hello: "Hello world"
@@ -1,43 +0,0 @@
1
- # Puma can serve each request in a thread from an internal thread pool.
2
- # The `threads` method setting takes two numbers: a minimum and maximum.
3
- # Any libraries that use thread pools should be configured to match
4
- # the maximum value specified for Puma. Default is set to 5 threads for minimum
5
- # and maximum; this matches the default thread size of Active Record.
6
- #
7
- max_threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }
8
- min_threads_count = ENV.fetch("RAILS_MIN_THREADS") { max_threads_count }
9
- threads min_threads_count, max_threads_count
10
-
11
- # Specifies the `worker_timeout` threshold that Puma will use to wait before
12
- # terminating a worker in development environments.
13
- #
14
- worker_timeout 3600 if ENV.fetch("RAILS_ENV", "development") == "development"
15
-
16
- # Specifies the `port` that Puma will listen on to receive requests; default is 3000.
17
- #
18
- port ENV.fetch("PORT") { 3000 }
19
-
20
- # Specifies the `environment` that Puma will run in.
21
- #
22
- environment ENV.fetch("RAILS_ENV") { "development" }
23
-
24
- # Specifies the `pidfile` that Puma will use.
25
- pidfile ENV.fetch("PIDFILE") { "tmp/pids/server.pid" }
26
-
27
- # Specifies the number of `workers` to boot in clustered mode.
28
- # Workers are forked web server processes. If using threads and workers together
29
- # the concurrency of the application would be max `threads` * `workers`.
30
- # Workers do not work on JRuby or Windows (both of which do not support
31
- # processes).
32
- #
33
- # workers ENV.fetch("WEB_CONCURRENCY") { 2 }
34
-
35
- # Use the `preload_app!` method when specifying a `workers` number.
36
- # This directive tells Puma to first boot the application and load code
37
- # before forking the application. This takes advantage of Copy On Write
38
- # process behavior so workers use less memory.
39
- #
40
- # preload_app!
41
-
42
- # Allow puma to be restarted by `bin/rails restart` command.
43
- plugin :tmp_restart
@@ -1,6 +0,0 @@
1
- Rails.application.routes.draw do
2
- # Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html
3
-
4
- # Defines the root path route ("/")
5
- # root "articles#index"
6
- end
@@ -1,34 +0,0 @@
1
- test:
2
- service: Disk
3
- root: <%= Rails.root.join("tmp/storage") %>
4
-
5
- local:
6
- service: Disk
7
- root: <%= Rails.root.join("storage") %>
8
-
9
- # Use bin/rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key)
10
- # amazon:
11
- # service: S3
12
- # access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %>
13
- # secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %>
14
- # region: us-east-1
15
- # bucket: your_own_bucket-<%= Rails.env %>
16
-
17
- # Remember not to checkin your GCS keyfile to a repository
18
- # google:
19
- # service: GCS
20
- # project: your_project
21
- # credentials: <%= Rails.root.join("path/to/gcs.keyfile") %>
22
- # bucket: your_own_bucket-<%= Rails.env %>
23
-
24
- # Use bin/rails credentials:edit to set the Azure Storage secret (as azure_storage:storage_access_key)
25
- # microsoft:
26
- # service: AzureStorage
27
- # storage_account_name: your_account_name
28
- # storage_access_key: <%= Rails.application.credentials.dig(:azure_storage, :storage_access_key) %>
29
- # container: your_container_name-<%= Rails.env %>
30
-
31
- # mirror:
32
- # service: Mirror
33
- # primary: local
34
- # mirrors: [ amazon, google, microsoft ]
@@ -1,6 +0,0 @@
1
- # This file is used by Rack-based servers to start the application.
2
-
3
- require_relative "config/environment"
4
-
5
- run Rails.application
6
- Rails.application.load_server
@@ -1,10 +0,0 @@
1
- class CreateGames < ActiveRecord::Migration[7.0]
2
- def change
3
- create_table :games do |t|
4
- t.string :name
5
- t.string :description
6
-
7
- t.timestamps
8
- end
9
- end
10
- end
@@ -1,19 +0,0 @@
1
- class CreatePlays < ActiveRecord::Migration[7.0]
2
- def change
3
- enable_extension("timescaledb") unless extensions.include? "timescaledb"
4
- hypertable_options = {
5
- time_column: 'created_at',
6
- chunk_time_interval: '1 day',
7
- compress_segmentby: 'game_id',
8
- compress_orderby: 'created_at',
9
- compression_interval: '7 days'
10
- }
11
- create_table :plays, hypertable: hypertable_options, id: false do |t|
12
- t.references :game, null: false, foreign_key: false
13
- t.integer :score
14
- t.decimal :total_time
15
-
16
- t.timestamps
17
- end
18
- end
19
- end
@@ -1,5 +0,0 @@
1
- class CreateScorePerHours < ActiveRecord::Migration[7.0]
2
- def change
3
- create_scenic_continuous_aggregate :score_per_hours
4
- end
5
- end
@@ -1,47 +0,0 @@
1
- # This file is auto-generated from the current state of the database. Instead
2
- # of editing this file, please use the migrations feature of Active Record to
3
- # incrementally modify your database, and then regenerate this schema definition.
4
- #
5
- # This file is the source Rails uses to define your schema when running `bin/rails
6
- # db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to
7
- # be faster and is potentially less error prone than running all of your
8
- # migrations from scratch. Old migrations may fail to apply correctly if those
9
- # migrations use external dependencies or application code.
10
- #
11
- # It's strongly recommended that you check this file into your version control system.
12
-
13
- ActiveRecord::Schema[7.0].define(version: 2022_02_09_143347) do
14
- # These are extensions that must be enabled in order to support this database
15
- enable_extension "plpgsql"
16
- enable_extension "timescaledb"
17
-
18
- create_table "games", force: :cascade do |t|
19
- t.string "name"
20
- t.string "description"
21
- t.datetime "created_at", null: false
22
- t.datetime "updated_at", null: false
23
- end
24
-
25
- create_table "plays", id: false, force: :cascade do |t|
26
- t.bigint "game_id", null: false
27
- t.integer "score"
28
- t.decimal "total_time"
29
- t.datetime "created_at", null: false
30
- t.datetime "updated_at", null: false
31
- t.index ["created_at"], name: "plays_created_at_idx", order: :desc
32
- t.index ["game_id"], name: "index_plays_on_game_id"
33
- end
34
-
35
- create_hypertable "plays", time_column: "created_at", chunk_time_interval: "1 minute", compress_segmentby: "game_id", compress_orderby: "created_at ASC", compression_interval: "P7D"
36
-
37
- create_continuous_aggregate("score_per_hours", <<-SQL)
38
- SELECT plays.game_id,
39
- time_bucket('PT1H'::interval, plays.created_at) AS bucket,
40
- avg(plays.score) AS avg,
41
- max(plays.score) AS max,
42
- min(plays.score) AS min
43
- FROM plays
44
- GROUP BY plays.game_id, (time_bucket('PT1H'::interval, plays.created_at))
45
- SQL
46
-
47
- end
@@ -1,7 +0,0 @@
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 bin/rails db:seed command (or created alongside the database with db:setup).
3
- #
4
- # Examples:
5
- #
6
- # movies = Movie.create([{ name: "Star Wars" }, { name: "Lord of the Rings" }])
7
- # Character.create(name: "Luke", movie: movies.first)
@@ -1,7 +0,0 @@
1
- SELECT game_id,
2
- time_bucket(INTERVAL '1 hour', created_at) AS bucket,
3
- AVG(score),
4
- MAX(score),
5
- MIN(score)
6
- FROM plays
7
- GROUP BY game_id, bucket;
File without changes
File without changes
@@ -1 +0,0 @@
1
- # See https://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -1,104 +0,0 @@
1
- # ruby compare_volatility.rb postgres://user:pass@host:port/db_name
2
- require 'bundler/inline' #require only what you need
3
-
4
- gemfile(true) do
5
- gem 'timescaledb', path: '../..'
6
- gem 'pry'
7
- end
8
-
9
- # TODO: get the volatility using the window function with plain postgresql
10
-
11
- ActiveRecord::Base.establish_connection ARGV.last
12
-
13
- # Compare volatility processing in Ruby vs SQL.
14
- class Measurement < ActiveRecord::Base
15
- acts_as_hypertable time_column: "ts"
16
- acts_as_time_vector segment_by: "device_id", value_column: "val"
17
-
18
- scope :volatility_sql, -> do
19
- select("device_id, timevector(#{time_column}, #{value_column}) -> sort() -> delta() -> abs() -> sum() as volatility")
20
- .group("device_id")
21
- end
22
-
23
- scope :volatility_ruby, -> {
24
- volatility = Hash.new(0)
25
- previous = Hash.new
26
- find_all do |measurement|
27
- device_id = measurement.device_id
28
- if previous[device_id]
29
- delta = (measurement.val - previous[device_id]).abs
30
- volatility[device_id] += delta
31
- end
32
- previous[device_id] = measurement.val
33
- end
34
- volatility
35
- }
36
- scope :values_from_devices, -> {
37
- ordered_values = select(:val, :device_id).order(:ts)
38
- Hash[
39
- from(ordered_values)
40
- .group(:device_id)
41
- .pluck("device_id, array_agg(val)")
42
- ]
43
- }
44
- end
45
-
46
- class Volatility
47
- def self.process(values)
48
- previous = nil
49
- deltas = values.map do |value|
50
- if previous
51
- delta = (value - previous).abs
52
- volatility = delta
53
- end
54
- previous = value
55
- volatility
56
- end
57
- #deltas => [nil, 1, 1]
58
- deltas.shift
59
- volatility = deltas.sum
60
- end
61
- def self.process_values(map)
62
- map.transform_values(&method(:process))
63
- end
64
- end
65
-
66
- ActiveRecord::Base.connection.add_toolkit_to_search_path!
67
-
68
-
69
- ActiveRecord::Base.connection.instance_exec do
70
- ActiveRecord::Base.logger = Logger.new(STDOUT)
71
-
72
- unless Measurement.table_exists?
73
- hypertable_options = {
74
- time_column: 'ts',
75
- chunk_time_interval: '1 day',
76
- }
77
- create_table :measurements, hypertable: hypertable_options, id: false do |t|
78
- t.integer :device_id
79
- t.decimal :val
80
- t.timestamp :ts
81
- end
82
- end
83
- end
84
-
85
- if Measurement.count.zero?
86
- ActiveRecord::Base.connection.execute(<<~SQL)
87
- INSERT INTO measurements (ts, device_id, val)
88
- SELECT ts, device_id, random()*80
89
- FROM generate_series(TIMESTAMP '2022-01-01 00:00:00',
90
- TIMESTAMP '2022-02-01 00:00:00',
91
- INTERVAL '5 minutes') AS g1(ts),
92
- generate_series(0, 5) AS g2(device_id);
93
- SQL
94
- end
95
-
96
-
97
- volatilities = nil
98
- #ActiveRecord::Base.logger = nil
99
- Benchmark.bm do |x|
100
- x.report("sql") { Measurement.volatility_sql.map(&:attributes) }
101
- x.report("ruby") { Measurement.volatility_ruby }
102
- x.report("fetch") { volatilities = Measurement.values_from_devices }
103
- x.report("process") { Volatility.process_values(volatilities) }
104
- end
@@ -1,15 +0,0 @@
1
- # LTTB examples
2
-
3
- This folder contains a few ideas to explore and learn more about the lttb algorithm.
4
-
5
- There is a [./lttb.rb](./lttb.rb) file that is the Ruby implementation of lttb
6
- and also contains the related [./lttb_test.rb](./lttb_test.rb) file that
7
- verifies the same example from the Timescale Toolkit [implementation](https://github.com/timescale/timescaledb-toolkit/blob/6ee2ea1e8ff64bab10b90bdf4cd4b0f7ed763934/extension/src/lttb.rs#L512-L530).
8
-
9
- The [./lttb_sinatra.rb](./lttb_sinatra.rb) is a small webserver that compares
10
- the SQL vs Ruby implementation. It also uses the [./views](./views) folder which
11
- contains the view rendering part.
12
-
13
- You can learn more by reading the [LTTB tutorial](https://jonatas.github.io/timescaledb/toolkit_lttb_tutorial/).
14
-
15
-
@@ -1,92 +0,0 @@
1
- module Triangle
2
- module_function
3
- def area(a, b, c)
4
- (ax, ay),(bx,by),(cx,cy) = a,b,c
5
- (
6
- (ax - cx).to_f * (by - ay) -
7
- (ax - bx).to_f * (cy - ay)
8
- ).abs * 0.5
9
- end
10
- end
11
- class Lttb
12
- class << self
13
- def avg(array)
14
- array.sum.to_f / array.size
15
- end
16
-
17
- def downsample(data, threshold)
18
- new(data, threshold).downsample
19
- end
20
- end
21
-
22
- attr_reader :data, :threshold
23
- def initialize(data, threshold)
24
- fail 'data is not an array' unless data.is_a? Array
25
- fail "threshold should be >= 2. It's #{threshold}." if threshold < 2
26
- @data = data
27
- @threshold = threshold
28
- end
29
-
30
- def downsample
31
- case @data.first.first
32
- when Time, DateTime, Date
33
- transformed_dates = true
34
- dates_to_numbers()
35
- end
36
- process.tap do |downsampled|
37
- numbers_to_dates(downsampled) if transformed_dates
38
- end
39
- end
40
- private
41
-
42
- def process
43
- return data if threshold >= data.size || threshold == 0
44
-
45
- sampled = [data.first, data.last] # Keep first and last point. append in the middle.
46
- point_index = 0
47
-
48
- (threshold - 2).times do |i|
49
- step = [((i+1.0) * bucket_size).to_i, data.size].min
50
- next_point = (i * bucket_size).to_i + 1
51
-
52
- break if next_point > data.size - 2
53
-
54
- points = data[step, slice]
55
- avg_x = Lttb.avg(points.map(&:first)).to_i
56
- avg_y = Lttb.avg(points.map(&:last))
57
-
58
- max_area = -1.0
59
-
60
- (next_point...(step + 1)).each do |idx|
61
- area = Triangle.area(data[point_index], data[idx], [avg_x, avg_y])
62
-
63
- if area > max_area
64
- max_area = area
65
- next_point = idx
66
- end
67
- end
68
-
69
- sampled.insert(-2, data[next_point])
70
- point_index = next_point
71
- end
72
-
73
- sampled
74
- end
75
-
76
- def bucket_size
77
- @bucket_size ||= ((data.size - 2.0) / (threshold - 2.0))
78
- end
79
-
80
- def slice
81
- @slice ||= bucket_size.to_i
82
- end
83
-
84
- def dates_to_numbers
85
- @start_date = data[0][0].dup
86
- data.each{|d| d[0] = d[0] - @start_date }
87
- end
88
-
89
- def numbers_to_dates(downsampled)
90
- downsampled.each{|d| d[0] = @start_date + d[0]}
91
- end
92
- end