vissen-output 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +8 -0
- data/.rubocop.yml +64 -0
- data/.travis.yml +5 -0
- data/CHANGELOG.md +61 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +46 -0
- data/LICENSE.txt +21 -0
- data/README.md +40 -0
- data/Rakefile +17 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/lib/vissen/output/buffer.rb +82 -0
- data/lib/vissen/output/color.rb +118 -0
- data/lib/vissen/output/context/circle.rb +70 -0
- data/lib/vissen/output/context/cloud.rb +130 -0
- data/lib/vissen/output/context/grid.rb +103 -0
- data/lib/vissen/output/context.rb +134 -0
- data/lib/vissen/output/context_error.rb +14 -0
- data/lib/vissen/output/error.rb +10 -0
- data/lib/vissen/output/filter/gamma.rb +38 -0
- data/lib/vissen/output/filter/quantizer.rb +64 -0
- data/lib/vissen/output/filter.rb +35 -0
- data/lib/vissen/output/palette.rb +112 -0
- data/lib/vissen/output/palettes.rb +12 -0
- data/lib/vissen/output/pixel.rb +26 -0
- data/lib/vissen/output/pixel_buffer.rb +71 -0
- data/lib/vissen/output/point.rb +57 -0
- data/lib/vissen/output/version.rb +8 -0
- data/lib/vissen/output/vixel.rb +64 -0
- data/lib/vissen/output/vixel_buffer.rb +56 -0
- data/lib/vissen/output/vixel_stack.rb +89 -0
- data/lib/vissen/output.rb +31 -0
- data/vissen-output.gemspec +31 -0
- metadata +148 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: ee97aa25edd367c68a4f113fac72270890711d5859eb61bec40a71470753fb8e
|
4
|
+
data.tar.gz: c1069056b86a4f7d62ab4e5d5602bcf5c8131d684e8dc9631955f2582d516e3a
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 7475b3b4f78d4fd8f0a656d9dc3e45ade1af5a06e1596df9faa5de8eaa508c3489001b714aaf8f509f9b09d9416ecd99c95ac8682aa17f4cc5ced85003299ad1
|
7
|
+
data.tar.gz: 9c552a4b52af3646f6459116e3dda5a297c147886f90c261bb85448b1c978eda53155f68028d186af7b7fa0a77c428e4c9e598ae2ae2ebfb67069e46f2ba54ab
|
data/.gitignore
ADDED
data/.rubocop.yml
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
AllCops:
|
2
|
+
Exclude:
|
3
|
+
- 'vendor/**/*'
|
4
|
+
- 'tmp/**/*'
|
5
|
+
TargetRubyVersion: 2.5
|
6
|
+
|
7
|
+
Style/FrozenStringLiteralComment:
|
8
|
+
EnforcedStyle: always
|
9
|
+
|
10
|
+
Layout/EndOfLine:
|
11
|
+
EnforcedStyle: lf
|
12
|
+
|
13
|
+
Layout/ClassStructure:
|
14
|
+
Enabled: true
|
15
|
+
Categories:
|
16
|
+
module_inclusion:
|
17
|
+
- include
|
18
|
+
- prepend
|
19
|
+
- extend
|
20
|
+
ExpectedOrder:
|
21
|
+
- module_inclusion
|
22
|
+
- constants
|
23
|
+
- public_class_methods
|
24
|
+
- initializer
|
25
|
+
- instance_methods
|
26
|
+
- protected_methods
|
27
|
+
- private_methods
|
28
|
+
|
29
|
+
Layout/IndentHeredoc:
|
30
|
+
EnforcedStyle: squiggly
|
31
|
+
|
32
|
+
Lint/AmbiguousBlockAssociation:
|
33
|
+
Exclude:
|
34
|
+
- 'test/**/*.rb'
|
35
|
+
|
36
|
+
Lint/InterpolationCheck:
|
37
|
+
Exclude:
|
38
|
+
- 'test/**/*.rb'
|
39
|
+
|
40
|
+
Metrics/BlockLength:
|
41
|
+
Exclude:
|
42
|
+
- 'Rakefile'
|
43
|
+
- '**/*.rake'
|
44
|
+
- 'test/**/*.rb'
|
45
|
+
|
46
|
+
Metrics/ModuleLength:
|
47
|
+
Exclude:
|
48
|
+
- 'test/**/*.rb'
|
49
|
+
|
50
|
+
Metrics/ParameterLists:
|
51
|
+
CountKeywordArgs: false
|
52
|
+
|
53
|
+
Naming/UncommunicativeMethodParamName:
|
54
|
+
AllowedNames:
|
55
|
+
- x
|
56
|
+
- y
|
57
|
+
- i
|
58
|
+
- p
|
59
|
+
- n
|
60
|
+
- r
|
61
|
+
- g
|
62
|
+
- b
|
63
|
+
- to
|
64
|
+
|
data/.travis.yml
ADDED
data/CHANGELOG.md
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
# Changelog
|
2
|
+
All notable changes to this project will be documented in this file.
|
3
|
+
|
4
|
+
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
|
5
|
+
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
|
6
|
+
|
7
|
+
## [Unreleased]
|
8
|
+
## [0.6.1] - 2018-04-20
|
9
|
+
### Changed
|
10
|
+
- Improved, more descriptive comments.
|
11
|
+
|
12
|
+
## [0.6.0] - 2018-04-14
|
13
|
+
### Added
|
14
|
+
- Output filter support in PixelBuffer.
|
15
|
+
- More descriptive ContextErrors that are raised when contexts do not match.
|
16
|
+
|
17
|
+
### Changed
|
18
|
+
- Grid context now accepts a with and height instead of an aspect ratio.
|
19
|
+
|
20
|
+
## [0.5.1] - 2018-04-14
|
21
|
+
### Added
|
22
|
+
- PixelBuffer#finalize!.
|
23
|
+
- The convenience method Context::Cloud.scatter that randomly places n points.
|
24
|
+
|
25
|
+
### Fixed
|
26
|
+
- A bug in the Circle context caused the circle to be centered in (0,0), rather than in the center of the context.
|
27
|
+
|
28
|
+
## [0.5.0] - 2018-04-13
|
29
|
+
### Changed
|
30
|
+
- Changed the name of the Cloud module to Buffer to distance it from the cloud context.
|
31
|
+
- Moved all context to their own submodule.
|
32
|
+
- Changed the name of the PixelCloud to PixelBuffer to better indicate what it should be used for.
|
33
|
+
- Changed the name of the VixelCloud to VixelBuffer to better indicate what it should be used for.
|
34
|
+
|
35
|
+
### Removed
|
36
|
+
- The Grid class.
|
37
|
+
|
38
|
+
## [0.4.1] - 2018-04-13
|
39
|
+
### Added
|
40
|
+
- A Circle context.
|
41
|
+
|
42
|
+
### Changed
|
43
|
+
- Improved the documentation.
|
44
|
+
|
45
|
+
## [0.4.0] - 2018-04-10
|
46
|
+
### Added
|
47
|
+
- Output filters.
|
48
|
+
- Gamma filter.
|
49
|
+
- Quantizer filter.
|
50
|
+
- Introduced the more general concept of point clouds.
|
51
|
+
- Created the Context as a more general form of the GridContext.
|
52
|
+
- Create a new CloudContext that handles arbitrarily positioned Point objects.
|
53
|
+
|
54
|
+
### Changed
|
55
|
+
- Made the Grid into a special kind of Cloud.
|
56
|
+
- Made the VixelGrid into a VixelCloud.
|
57
|
+
- Made the PixelGrid into a PixelCloud.
|
58
|
+
- The GridContext is now a class instead of a module.
|
59
|
+
- The Stack is now longer a GridContext but instead accepts one as its first argument.
|
60
|
+
- The argument order of Vixel.new is now reversed, so that is is alphabetical.
|
61
|
+
- Renamed Stack -> VixelStack.
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
vissen-output (0.6.1)
|
5
|
+
|
6
|
+
GEM
|
7
|
+
remote: https://rubygems.org/
|
8
|
+
specs:
|
9
|
+
ast (2.4.0)
|
10
|
+
docile (1.3.0)
|
11
|
+
json (2.1.0)
|
12
|
+
minitest (5.11.3)
|
13
|
+
parallel (1.12.1)
|
14
|
+
parser (2.5.1.0)
|
15
|
+
ast (~> 2.4.0)
|
16
|
+
powerpack (0.1.1)
|
17
|
+
rainbow (3.0.0)
|
18
|
+
rake (10.5.0)
|
19
|
+
rubocop (0.55.0)
|
20
|
+
parallel (~> 1.10)
|
21
|
+
parser (>= 2.5)
|
22
|
+
powerpack (~> 0.1)
|
23
|
+
rainbow (>= 2.2.2, < 4.0)
|
24
|
+
ruby-progressbar (~> 1.7)
|
25
|
+
unicode-display_width (~> 1.0, >= 1.0.1)
|
26
|
+
ruby-progressbar (1.9.0)
|
27
|
+
simplecov (0.16.1)
|
28
|
+
docile (~> 1.1)
|
29
|
+
json (>= 1.8, < 3)
|
30
|
+
simplecov-html (~> 0.10.0)
|
31
|
+
simplecov-html (0.10.2)
|
32
|
+
unicode-display_width (1.3.0)
|
33
|
+
|
34
|
+
PLATFORMS
|
35
|
+
ruby
|
36
|
+
|
37
|
+
DEPENDENCIES
|
38
|
+
bundler (~> 1.16)
|
39
|
+
minitest (~> 5.0)
|
40
|
+
rake (~> 10.0)
|
41
|
+
rubocop (~> 0.52)
|
42
|
+
simplecov (~> 0.16)
|
43
|
+
vissen-output!
|
44
|
+
|
45
|
+
BUNDLED WITH
|
46
|
+
1.16.1
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2018 Sebastian Lindberg
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
# Vissen::Output
|
2
|
+
|
3
|
+
[](https://travis-ci.org/midi-visualizer/vissen-output)
|
4
|
+
[](http://inch-ci.org/github/midi-visualizer/vissen-output)
|
5
|
+
|
6
|
+
The Vissen Output library implements the objects used by the Vissen Engine to talk to the various sinks.
|
7
|
+
|
8
|
+
## Installation
|
9
|
+
|
10
|
+
Add this line to your application's Gemfile:
|
11
|
+
|
12
|
+
```ruby
|
13
|
+
gem 'vissen-output'
|
14
|
+
```
|
15
|
+
|
16
|
+
And then execute:
|
17
|
+
|
18
|
+
$ bundle
|
19
|
+
|
20
|
+
Or install it yourself as:
|
21
|
+
|
22
|
+
$ gem install vissen-output
|
23
|
+
|
24
|
+
## Usage
|
25
|
+
|
26
|
+
TODO: Write usage instructions here
|
27
|
+
|
28
|
+
## Development
|
29
|
+
|
30
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
31
|
+
|
32
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
33
|
+
|
34
|
+
## Contributing
|
35
|
+
|
36
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/midi-visualizer/vissen-output.
|
37
|
+
|
38
|
+
## License
|
39
|
+
|
40
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler/gem_tasks'
|
4
|
+
require 'rake/testtask'
|
5
|
+
require 'rubocop/rake_task'
|
6
|
+
|
7
|
+
Rake::TestTask.new(:test) do |t|
|
8
|
+
t.libs << 'test'
|
9
|
+
t.libs << 'lib'
|
10
|
+
t.test_files = FileList['test/**/*_test.rb']
|
11
|
+
end
|
12
|
+
|
13
|
+
RuboCop::RakeTask.new(:rubocop) do |t|
|
14
|
+
t.options = ['-a']
|
15
|
+
end
|
16
|
+
|
17
|
+
task default: :test
|
data/bin/console
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'bundler/setup'
|
5
|
+
require 'vissen/output'
|
6
|
+
|
7
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
8
|
+
# with your gem easier. You can also use a different console, if you like.
|
9
|
+
|
10
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
11
|
+
# require 'pry'
|
12
|
+
# Pry.start
|
13
|
+
|
14
|
+
require 'irb'
|
15
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'forwardable'
|
4
|
+
|
5
|
+
module Vissen
|
6
|
+
module Output
|
7
|
+
# Buffer
|
8
|
+
#
|
9
|
+
#
|
10
|
+
module Buffer
|
11
|
+
extend Forwardable
|
12
|
+
|
13
|
+
# @return [Context] the context of the buffer.
|
14
|
+
attr_reader :context
|
15
|
+
|
16
|
+
# @return [Object] the elements at the buffer points.
|
17
|
+
attr_reader :elements
|
18
|
+
|
19
|
+
def_delegators :@context, :width, :height
|
20
|
+
def_delegators :@elements, :each, :each_with_index
|
21
|
+
|
22
|
+
# The grid is setup with a grid context as well as a class to places
|
23
|
+
# instances of in every grid point.
|
24
|
+
#
|
25
|
+
# @raise [ArgumentError] if both an element class and a block are given.
|
26
|
+
#
|
27
|
+
# @param context [Context] the context in which the buffer exists.
|
28
|
+
# @param elements_klass [Class] the class to use when allocating
|
29
|
+
# elements.
|
30
|
+
# @param block [Proc] the block to use instead of `elements_klass` when
|
31
|
+
# allocating element objects.
|
32
|
+
def initialize(context, elements_klass = nil, &block)
|
33
|
+
@context = context
|
34
|
+
@elements =
|
35
|
+
if block_given?
|
36
|
+
raise ArgumentError if elements_klass
|
37
|
+
context.alloc_points(&block).freeze
|
38
|
+
else
|
39
|
+
context.alloc_points(elements_klass).freeze
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# Prevents the context and element array from being changed.
|
44
|
+
#
|
45
|
+
# @return [self]
|
46
|
+
def freeze
|
47
|
+
@elements.freeze
|
48
|
+
super
|
49
|
+
end
|
50
|
+
|
51
|
+
# Context specific element accessor. Depends on `Context#index_from` to
|
52
|
+
# transform `args` into an index.
|
53
|
+
#
|
54
|
+
# @param args (see Context#index_from).
|
55
|
+
# @return [Object] the element at the given index.
|
56
|
+
def [](*args)
|
57
|
+
@elements[@context.index_from(*args)]
|
58
|
+
end
|
59
|
+
|
60
|
+
# Iterates over each element in the buffer and yields the element along
|
61
|
+
# with its x and y coordinates.
|
62
|
+
#
|
63
|
+
# @return (see Context#each_position).
|
64
|
+
def each_with_position
|
65
|
+
return to_enum(__callee__) unless block_given?
|
66
|
+
@context.each_position { |i, x, y| yield @elements[i], x, y }
|
67
|
+
end
|
68
|
+
|
69
|
+
# Two buffers are considered equal if they share the same context.
|
70
|
+
#
|
71
|
+
# @param other [#context, Object]
|
72
|
+
# @return [true, false] true if the other object share the same context.
|
73
|
+
def share_context?(other)
|
74
|
+
@context == other.context
|
75
|
+
rescue NoMethodError
|
76
|
+
false
|
77
|
+
end
|
78
|
+
|
79
|
+
alias === share_context?
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Vissen
|
4
|
+
module Output
|
5
|
+
# Basic value object representing a color in the RGB color space.
|
6
|
+
#
|
7
|
+
# == Usage
|
8
|
+
# The following example creates two colors, mixes them, and converts the
|
9
|
+
# result to an array of color component.
|
10
|
+
#
|
11
|
+
# color_a = Color.new 0.3, 0.6, 0.2
|
12
|
+
# color_b = Color.new 0.1, 0.2, 0.5
|
13
|
+
#
|
14
|
+
# color_a.mix_with(color_b, 0.5).to_a => [0.2, 0.3, 0.35]
|
15
|
+
#
|
16
|
+
class Color
|
17
|
+
# Accessors for the red, green and blue color components.
|
18
|
+
attr_accessor :r, :g, :b
|
19
|
+
|
20
|
+
# @param r [Float] the red color value in the range (0..1).
|
21
|
+
# @param g [Float] the green color value in the range (0..1).
|
22
|
+
# @param b [Float] the blue color value in the range (0..1).
|
23
|
+
def initialize(r = 0.0, g = 0.0, b = 0.0)
|
24
|
+
@r = r
|
25
|
+
@g = g
|
26
|
+
@b = b
|
27
|
+
end
|
28
|
+
|
29
|
+
# Color equality based on component values.
|
30
|
+
#
|
31
|
+
# TODO: Add some small delta around what is considered the same color?
|
32
|
+
# Perhaps scale to 255 and floor before comparing?
|
33
|
+
#
|
34
|
+
# @param other [Object] the object to check equality against.
|
35
|
+
# @return [true, false] true when two colors are exactly the same.
|
36
|
+
def ==(other)
|
37
|
+
r == other.r && g == other.g && b == other.b
|
38
|
+
rescue NoMethodError
|
39
|
+
false
|
40
|
+
end
|
41
|
+
|
42
|
+
# Creates a new array from the color.
|
43
|
+
#
|
44
|
+
# @return [Array<Float>] a new array containing the red, green and blue
|
45
|
+
# color values.
|
46
|
+
def to_a
|
47
|
+
[r, g, b]
|
48
|
+
end
|
49
|
+
|
50
|
+
# rubocop:disable Metrics/AbcSize
|
51
|
+
|
52
|
+
# Moves this color toword the other based on the given ratio.
|
53
|
+
#
|
54
|
+
# ratio = 0 -> 100 % of this color
|
55
|
+
# ratio = 1 -> 100 % of the other color
|
56
|
+
#
|
57
|
+
# @param other [Color] the color to mix with
|
58
|
+
# @param ratio [Float] the amount (0..1) of the other color to mix in.
|
59
|
+
# @return [self]
|
60
|
+
def mix_with!(other, ratio)
|
61
|
+
anti_ratio = (1 - ratio)
|
62
|
+
|
63
|
+
self.r = r * anti_ratio + other.r * ratio
|
64
|
+
self.g = g * anti_ratio + other.g * ratio
|
65
|
+
self.b = b * anti_ratio + other.b * ratio
|
66
|
+
|
67
|
+
self
|
68
|
+
end
|
69
|
+
# rubocop:enable Metrics/AbcSize
|
70
|
+
|
71
|
+
# Returns a new color that is a mix between this and the other color,
|
72
|
+
# based on the ratio. See `#mix_with!` for more details.
|
73
|
+
#
|
74
|
+
# @param (see #mix_with!)
|
75
|
+
# @return [Color] the result of the mix.
|
76
|
+
def mix_with(other, ratio)
|
77
|
+
dup.mix_with! other, ratio
|
78
|
+
end
|
79
|
+
|
80
|
+
# Returns a string formatted in the tradiotioal hex representation of a
|
81
|
+
# color: #04A4BF.
|
82
|
+
#
|
83
|
+
# @return [String] the object string representation.
|
84
|
+
def inspect
|
85
|
+
format('#%02X%02X%02X', *to_a.map! { |v| (v * 255).round })
|
86
|
+
end
|
87
|
+
|
88
|
+
class << self
|
89
|
+
# Cast a given object to a color.
|
90
|
+
#
|
91
|
+
# @param obj [Color, Array<Numeric>, Integer, #to_a] the object to
|
92
|
+
# coerce.
|
93
|
+
# @return [Color] a new color object.
|
94
|
+
def from(obj)
|
95
|
+
case obj
|
96
|
+
when self then obj
|
97
|
+
when Array then new(*obj)
|
98
|
+
when Integer then from_integer obj
|
99
|
+
else
|
100
|
+
new(*obj.to_a)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
private
|
105
|
+
|
106
|
+
def from_integer(int)
|
107
|
+
b = (int & 0xFF) / 255.0
|
108
|
+
int >>= 8
|
109
|
+
g = (int & 0xFF) / 255.0
|
110
|
+
int >>= 8
|
111
|
+
r = (int & 0xFF) / 255.0
|
112
|
+
|
113
|
+
new r, g, b
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Vissen
|
4
|
+
module Output
|
5
|
+
module Context
|
6
|
+
# Output context with the points placed counter clockwise on a circle. By
|
7
|
+
# specifying an offset it is possible to adjust the position of the end
|
8
|
+
# points of the element array.
|
9
|
+
class Circle < Cloud
|
10
|
+
# @param point_count [Integer] the number of points.
|
11
|
+
# @param offset [Numeric] the angle offset, in radians, of the first
|
12
|
+
# point.
|
13
|
+
# @param width [Numeric] (see Context)
|
14
|
+
# @param height [Numeric] (see Context)
|
15
|
+
# @param radius [Numeric] the radius of the context.
|
16
|
+
# @param args (see CloudContext).
|
17
|
+
def initialize(point_count,
|
18
|
+
offset: 0,
|
19
|
+
width: 1.0,
|
20
|
+
height: 1.0,
|
21
|
+
radius: [width, height].min / 2.0,
|
22
|
+
**args)
|
23
|
+
|
24
|
+
circle = self.class.position_generator(point_count, radius, offset)
|
25
|
+
center = [width.to_f / 2, height.to_f / 2]
|
26
|
+
points = self.class.place_points circle, center
|
27
|
+
|
28
|
+
super(points, width: width, height: height, **args)
|
29
|
+
end
|
30
|
+
|
31
|
+
class << self
|
32
|
+
# Creates a generator (`Enumerator`) for x and y coordinates
|
33
|
+
# equidistantly placed along a circle centered around (0, 0).
|
34
|
+
#
|
35
|
+
# @param point_count [Integer] the number of points along the circle.
|
36
|
+
# @param radius [Numeric] the radius of the circle.
|
37
|
+
# @param offset [Numeric] the angular offset of the first point. An
|
38
|
+
# offset of pi/2 would place the first point at the twelve o'clock
|
39
|
+
# position.
|
40
|
+
# @return [Enumerator] an enumerator that yields `point_count` x and y
|
41
|
+
# coordinates along a circle.
|
42
|
+
def position_generator(point_count, radius, offset = 0)
|
43
|
+
angle_factor = 2.0 * Math::PI / point_count
|
44
|
+
|
45
|
+
Enumerator.new(point_count) do |y|
|
46
|
+
point_count.times do |index|
|
47
|
+
angle = index * angle_factor + offset
|
48
|
+
y << [radius * Math.cos(angle), radius * Math.sin(angle)]
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# Uses a position generator to allocate an array of `Point` objects
|
54
|
+
# placed arount a circle centered around the given coordinates.
|
55
|
+
#
|
56
|
+
# @param generator [Enumerator] the position generator
|
57
|
+
# (see .position_generator).
|
58
|
+
# @param center [Array<Numeric>] the x and y coordinates of the
|
59
|
+
# circle center.
|
60
|
+
# @return [Array<Point>] an array of `Point` objects placed around a
|
61
|
+
# circle.
|
62
|
+
def place_points(generator, center)
|
63
|
+
x0, y0 = center
|
64
|
+
generator.map { |x, y| Point.new x0 + x, y0 + y }
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|