tty-sparkline 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 72e8765643f86155bdfe16df48b7342424e37d91758eead94bc23a981c4fc092
4
+ data.tar.gz: '094ae4841f7784550040d5e2036ffb222e8412bc0f617e7e9374440b4c6c6148'
5
+ SHA512:
6
+ metadata.gz: 660020d0cd95f23f1739d64f2e17bada95fd8db104746c1be3fa0e28ddbef97e82f9e857c108da117555bd555a43204f9cb3e5facd7ab4a1f70122752a1b1ac2
7
+ data.tar.gz: 1035f0cd07ccea2f1a918d504bab82398b3b5581a1274536440b7d4005814d02435c0267bb38e6742299983bf562d3a6c7d00c5a7560fddb0fa11ac31f272d9c
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ ## [v0.1.0] - 2021-05-20
2
+
3
+ * Initial release
4
+
5
+ [v0.1.0]: https://github.com/piotrmurach/tty-sparkline/compare/469d490...v0.1.0
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2021 Piotr Murach (piotrmurach.com)
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,490 @@
1
+ <div align="center">
2
+ <a href="https://ttytoolkit.org"><img width="130" src="https://github.com/piotrmurach/tty/raw/master/images/tty.png" alt="TTY Toolkit logo" /></a>
3
+ </div>
4
+
5
+ # TTY::Sparkline
6
+
7
+ [![Gem Version](https://badge.fury.io/rb/tty-sparkline.svg)][gem]
8
+ [![Actions CI](https://github.com/piotrmurach/tty-sparkline/workflows/CI/badge.svg?branch=master)][gh_actions_ci]
9
+ [![Build status](https://ci.appveyor.com/api/projects/status/3pk69ulk2fs330ja?svg=true)][appveyor]
10
+ [![Maintainability](https://api.codeclimate.com/v1/badges/aade5679e021d0ca5b55/maintainability)][codeclimate]
11
+ [![Coverage Status](https://coveralls.io/repos/github/piotrmurach/tty-sparkline/badge.svg)][coverage]
12
+ [![Inline docs](https://inch-ci.org/github/piotrmurach/tty-sparkline.svg?branch=master)][inchpages]
13
+
14
+ [gem]: https://badge.fury.io/rb/tty-sparkline
15
+ [gh_actions_ci]: https://github.com/piotrmurach/tty-sparkline/actions?query=workflow%3ACI
16
+ [appveyor]: https://ci.appveyor.com/project/piotrmurach/tty-sparkline
17
+ [codeclimate]: https://codeclimate.com/github/piotrmurach/tty-sparkline/maintainability
18
+ [coverage]: https://coveralls.io/github/piotrmurach/tty-sparkline
19
+ [inchpages]: https://inch-ci.org/github/piotrmurach/tty-sparkline
20
+
21
+ > Sparkline charts for terminal applications.
22
+
23
+ **TTY::Sparkline** provides sparkline drawing component for [TTY](https://github.com/piotrmurach/tty) toolkit.
24
+
25
+ ![tty-sparkline](https://github.com/piotrmurach/tty-sparkline/raw/master/assets/tty-sparkline.png)
26
+
27
+ ## Installation
28
+
29
+ Add this line to your application's Gemfile:
30
+
31
+ ```ruby
32
+ gem "tty-sparkline"
33
+ ```
34
+
35
+ And then execute:
36
+
37
+ $ bundle install
38
+
39
+ Or install it yourself as:
40
+
41
+ $ gem install tty-sparkline
42
+
43
+ ## Contents
44
+
45
+ * [1. Usage](#1-usage)
46
+ * [2. Interface](#2-interface)
47
+ * [2.1 new](#21-new)
48
+ * [2.2 append](#22-append)
49
+ * [2.3 render](#23-render)
50
+ * [3. Configuration](#3-configuration)
51
+ * [3.1 :top](#31-top)
52
+ * [3.2 :left](#32-left)
53
+ * [3.3 :height](#33-height)
54
+ * [3.4 :width](#34-width)
55
+ * [3.5 :min](#35-min)
56
+ * [3.6 :max](#36-max)
57
+ * [3.7 :bars](#37-bars)
58
+ * [3.8 :buffer_size](#38-buffer_size)
59
+ * [3.9 :non_numeric](#39-non_numeric)
60
+
61
+ ## 1. Usage
62
+
63
+ To display a sparkline chart, first, create an instance with some data:
64
+
65
+ ```ruby
66
+ sparkline = TTY::Sparkline.new(1..8)
67
+ ```
68
+
69
+ Then invoke `render` to generate the chart:
70
+
71
+ ```ruby
72
+ sparkline.render
73
+ # => "▁▂▃▄▅▆▇█"
74
+ ```
75
+
76
+ To display the sparkline in the terminal, print it like so:
77
+
78
+ ```ruby
79
+ print sparkline.render
80
+ ```
81
+
82
+ This will result in the following output:
83
+
84
+ ```
85
+ ▁▂▃▄▅▆▇█
86
+ ```
87
+
88
+ A sparkline can be positioned anywhere within the terminal screen using [:top](#31-top) and [:left](#32-left) keywords:
89
+
90
+ ```ruby
91
+ sparkline = TTY::Sparkline.new(1..8, top: 10, left: 50)
92
+ ```
93
+
94
+ A sparkline can have custom size given by the [:height](#33-height) and [:width](#34-width) keywords:
95
+
96
+ ```ruby
97
+ sparkline = TTY::Sparkline.new(1..8, height: 5, width: 100)
98
+ ```
99
+
100
+ To restrict data values use [:min](#35-min) and [:max](#36-max):
101
+
102
+ ```ruby
103
+ sparkline = TTY::Sparkline.new(1..8, min: 0, max: 5)
104
+ ```
105
+
106
+ ## 2. Interface
107
+
108
+ ### 2.1 new
109
+
110
+ To render a new sparkline chart, first, create an instance:
111
+
112
+ ```ruby
113
+ sparkline = TTY::Sparkline.new
114
+ ```
115
+
116
+ If you have a static set of data items you can provide them during initialisation with the `:data` keyword that accepts both `Array` and `Range` types:
117
+
118
+ ```ruby
119
+ sparkline = TTY::Sparkline.new(1..8)
120
+ sparkline = TTY::Sparkline.new([1, 2, 3, 4, 5, 6, 7, 8])
121
+ ```
122
+
123
+ However, if you don't know data upfront you can [append](#22-append) it after instantiation:
124
+
125
+ ```ruby
126
+ sparkline << 1 << 2 << 3 << 4
127
+ ```
128
+
129
+ ### 2.2 append
130
+
131
+ After a sparkline is created, you can further add more data items using the `push` method:
132
+
133
+ ```ruby
134
+ sparkline.push(1).push(2)
135
+ sparkline.push(3, 4)
136
+ ```
137
+
138
+ The `push` is also aliased as `append` and `<<` operator:
139
+
140
+ ```ruby
141
+ sparkline.append(5).append(6)
142
+ sparkline << 7 << 8
143
+ ```
144
+
145
+ Then calling `render` will result in the following:
146
+
147
+ ```ruby
148
+ sparkline.render
149
+ # => "▁▂▃▄▅▆▇█"
150
+ ````
151
+
152
+ This method is particularly useful if you intend to stream data. When streaming it's advisable to specify the maximum [width](#34-width) and position that the chart is going to span:
153
+
154
+ ```ruby
155
+ sparkline = TTY::Sparkline.new(left: 0, top: 0, width: 50)
156
+ ````
157
+
158
+ And then you can stream data, for example, from some service like so:
159
+
160
+ ```ruby
161
+ loop do
162
+ # fetch next value(s) from another service etc.
163
+
164
+ sparkline << value # send value(s) to the chart
165
+ print sparkline.render # render the chart out to the console
166
+ end
167
+ ```
168
+
169
+ ### 2.3 render
170
+
171
+ Once you have a sparkline instance with some data:
172
+
173
+ ```ruby
174
+ sparkline = TTY::Sparkline.new(1..8)
175
+ ```
176
+
177
+ To show the sparkline chart use the `render` method like so:
178
+
179
+ ```ruby
180
+ sparkline.render
181
+ # => "▁▂▃▄▅▆▇█"
182
+ ```
183
+
184
+ To display the chart in the terminal, you need to print it:
185
+
186
+ ```ruby
187
+ print sparkline.render
188
+ ```
189
+
190
+ This will result in the following output:
191
+
192
+ ```
193
+ ▁▂▃▄▅▆▇█
194
+ ```
195
+
196
+ The render method also accepts a block that provides access to the current value, bar character, column and row.
197
+
198
+ For example, to insert a space between each bar, we could do the following:
199
+
200
+ ```ruby
201
+ sparkline.render do |val, bar, col, row|
202
+ col == 7 ? bar : "#{bar} "
203
+ end
204
+ ```
205
+
206
+ That would result in:
207
+
208
+ ```ruby
209
+ # => "▁ ▂ ▃ ▄ ▅ ▆ ▇ █"
210
+ ```
211
+
212
+ The block form is useful for applying colours, for example, to mark the lowest or the highest value.
213
+
214
+ For instance, you can use [pastel](https://github.com/piotrmurach/pastel) to colour the maximum cyan, minimum red and all the other bars green:
215
+
216
+
217
+ ```ruby
218
+ pastel = Pastel.new
219
+
220
+ sparkline.render do |val, bar, col, row|
221
+ if val == 8
222
+ pastel.cyan(bar)
223
+ elsif val == 1
224
+ pastel.red(bar)
225
+ else
226
+ pastel.green(bar)
227
+ end
228
+ end
229
+ ```
230
+
231
+ ## 3. Configuration
232
+
233
+ ### 3.1 `:top`
234
+
235
+ By default, a sparkline will not be positioned. To position it against the top of the terminal screen use `:top` keyword. For example, to place a sparkline 10 lines down from the top of the screen do:
236
+
237
+ ```ruby
238
+ TTY::Sparkline.new(top: 10)
239
+ ```
240
+
241
+ Add [:left](#32-left) to position against the left of the terminal screen.
242
+
243
+ To dynamically position a sparkline within a terminal window consider using [tty-screen](https://github.com/piotrmurach/tty-screen) for gathering screen size. For example, to centre align against the vertical axis do:
244
+
245
+ ```ruby
246
+ TTY::Sparkline.new(top: TTY::Screen.height / 2)
247
+ ```
248
+
249
+ ### 3.2 `:left`
250
+
251
+ By default, a sparkline will not be positioned. To position it against the left side of the terminal screen use `:left` keyword. For example, to place a sparkline `50` columns away from the left side of the terminal window do:
252
+
253
+ ```ruby
254
+ TTY::Sparkline.new(left: 50)
255
+ ```
256
+
257
+ Add [:top](#31-top) to position against the top of the terminal screen.
258
+
259
+ To dynamically position a sparkline within a terminal window consider using [tty-screen](https://github.com/piotrmurach/tty-screen) for gathering screen size. For example, to centre align against horizontal axis do:
260
+
261
+ ```ruby
262
+ TTY::Sparkline.new(left: TTY::Screen.width / 2)
263
+ ```
264
+
265
+ ### 3.3 `:height`
266
+
267
+ By default, a sparkline will be rendered within a single terminal line. To change a chart to span more than one line use `:height` keyword. For example to display a sparkline on `3` lines do:
268
+
269
+ ```ruby
270
+ TTY::Sparkline.new(1..8, height: 3)
271
+ ```
272
+
273
+ Then rendering the sparkline in the console:
274
+
275
+ ```ruby
276
+ print sparkline.render
277
+ ````
278
+
279
+ We would get the following output:
280
+
281
+ ```
282
+ ▃▆
283
+ ▂▅███
284
+ ▁▄▇█████
285
+ ```
286
+
287
+ ### 3.4 `:width`
288
+
289
+ By default, a sparkline will be rendered in as many terminal columns as there are data items. To restrict the chart to a limited number of columns use the `:width` keyword. For example, to display a sparkline up to a maximum of `5` columns do:
290
+
291
+ ```ruby
292
+ TTY::Sparkline.new(1..8, width: 5)
293
+ ```
294
+
295
+ Then by rendering the sparkline in the console:
296
+
297
+ ```ruby
298
+ print sparkline.render
299
+ ```
300
+
301
+ We would generate the following limited output:
302
+
303
+ ```
304
+ ▄▅▆▇█
305
+ ```
306
+
307
+ This option can be combined with the [:height](#33-height):
308
+
309
+ ```ruby
310
+ TTY::Sparkline.new(1..8, height: 3, width: 5)
311
+ ```
312
+
313
+ The result of rendering the above sparkline would be as follows:
314
+
315
+ ```
316
+ ▃▆
317
+ ▂▅███
318
+ █████
319
+ ```
320
+
321
+ ### 3.5 `:min`
322
+
323
+ By default, the minimum value will be calculated from the entire data set. For example, given the following data:
324
+
325
+ ```ruby
326
+ sparkline = TTY::Sparkline.new([100, 75, 100, 50, 80])
327
+ ```
328
+
329
+ When displayed in the console, you will see the following:
330
+
331
+ ```
332
+ █▄█▁▅
333
+ ```
334
+
335
+ You will notice that the value of `50` looks like it's almost zero. This is because the sparkline automatically scales bars against the minimum value to make the differences between values more visible.
336
+
337
+ To change this, you can provide a custom minimum value using the `:min` keyword:
338
+
339
+ ```ruby
340
+ sparkline = TTY::Sparkline.new([100, 75, 100, 50, 80], min: 0)
341
+ ```
342
+
343
+ This time the display will show `50` as a more prominent value at the cost of making the difference between other values less striking:
344
+
345
+ ```
346
+ █▆█▄▆
347
+ ```
348
+
349
+ ### 3.6 `:max`
350
+
351
+ By default, the maximum value will be calculated from the entire data set. For example, given the following data:
352
+
353
+ ```ruby
354
+ sparkline = TTY::Sparkline.new([100, 75, 300, 50, 80])
355
+ ```
356
+
357
+ When displayed in the console, you will see the following:
358
+
359
+ ```
360
+ ▂▁█▁▁
361
+ ```
362
+
363
+ You will notice that the value of `300` makes all the remaining numbers look like insignificant zeros. This is because the sparkline automatically scales bars against the maximum value to make the differences between values more prominent.
364
+
365
+ To change this, you can provide a custom maximum value using the `:max` keyword:
366
+
367
+ ```ruby
368
+ sparkline = TTY::Sparkline.new([100, 75, 300, 50, 80], max: 100)
369
+ ```
370
+
371
+ This time the display will limit values and reduce `300` to the height of `100` making all the remaining values more visible.
372
+
373
+ ```
374
+ █▄█▁▅
375
+ ```
376
+
377
+ ### 3.7 `:bars`
378
+
379
+ There are eight bar types used to display values in a sparkline chart. These can be changed with the `:bars` keyword to a different set of characters:
380
+
381
+ ```ruby
382
+ sparkline = TTY::Sparkline.new(1..8, bars: %w[_ - = ^])
383
+ ```
384
+
385
+ Then rendering the chart will output:
386
+
387
+ ```ruby
388
+ sparkline.render
389
+ # => "___--==^"
390
+ ```
391
+
392
+ ### 3.8 `:buffer_size`
393
+
394
+ By default, the amount of data values that can be [appended](#22-append) to a sparkline is limited to `16K`. This helps to keep the memory size down when streaming data. You can change it with the `:buffer_size` keyword.
395
+
396
+ For example, to keep the last 5 data values do:
397
+
398
+ ```ruby
399
+ sparkline = TTY::Sparkline.new(buffer_size: 5, min: 0)
400
+ ```
401
+
402
+ Then exceeding the number of values:
403
+
404
+ ```ruby
405
+ sparkline.push(1, 2, 3, 4, 5, 6, 7, 8)
406
+ ```
407
+
408
+ Will cause the render to truncate the output:
409
+
410
+ ```ruby
411
+ sparkline.render
412
+ # => "▄▅▆▇█"
413
+ ````
414
+
415
+ ### 3.9 `:non_numeric`
416
+
417
+ To instruct a sparkline on how to deal with non-numeric data use the `:non_numeric` keyword.
418
+
419
+ Below are the possible values:
420
+
421
+ * `:empty` - shows empty spaces (default)
422
+ * `:ignore` - skips displaying anything
423
+ * `:minimum` - shows the smallest bar
424
+
425
+ Given data with some non-numeric values like `"foo"`, `nil` and `""`:
426
+
427
+ ```ruby
428
+ data = [1, 2, "foo", 4, nil, 6, "", 8]
429
+ ```
430
+
431
+ When you don't specify the `:non_numeric` keyword, it will be set to `:empty` by default:
432
+
433
+ ```ruby
434
+ sparkline = TTY::Sparkline.new(data)
435
+ ```
436
+
437
+ This will then display any non-numeric values as empty spaces:
438
+
439
+ ```ruby
440
+ sparkline.render
441
+ # => "▁▂ ▄ ▆ █"
442
+ ```
443
+
444
+ If you want to ignore displaying any non-numeric values use `:ignore`:
445
+
446
+ ```ruby
447
+ sparkline = TTY::Sparkline.new(data, non_numeric: :ignore)
448
+ ```
449
+
450
+ When rendered, this will result in:
451
+
452
+ ```ruby
453
+ sparkline.render
454
+ # => "▁▂▄▆█"
455
+ ````
456
+
457
+ You can also convert any non-numeric values to the smallest bar with `:minimum`:
458
+
459
+ ```ruby
460
+ sparkline = TTY::Sparkline.new(data, non_numeric: :minimum)
461
+ ```
462
+
463
+ This will render the following:
464
+
465
+ ```ruby
466
+ sparkline.render
467
+ # => "▁▂▁▄▁▆▁█"
468
+ ```
469
+
470
+ ## Development
471
+
472
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
473
+
474
+ 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 the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
475
+
476
+ ## Contributing
477
+
478
+ Bug reports and pull requests are welcome on GitHub at https://github.com/piotrmurach/tty-sparkline. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/piotrmurach/tty-sparkline/blob/master/CODE_OF_CONDUCT.md).
479
+
480
+ ## License
481
+
482
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
483
+
484
+ ## Code of Conduct
485
+
486
+ Everyone interacting in the TTY::Sparkline project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/piotrmurach/tty-sparkline/blob/master/CODE_OF_CONDUCT.md).
487
+
488
+ ## Copyright
489
+
490
+ Copyright (c) 2021 Piotr Murach. See LICENSE for further details.
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "tty/sparkline"
@@ -0,0 +1,330 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "tty-cursor"
4
+
5
+ require_relative "sparkline/version"
6
+
7
+ module TTY
8
+ # Responsible for drawing sparkline in a terminal
9
+ #
10
+ # @api public
11
+ class Sparkline
12
+ class Error < StandardError; end
13
+
14
+ BARS = %w[▁ ▂ ▃ ▄ ▅ ▆ ▇ █].freeze
15
+
16
+ EMPTY = ""
17
+
18
+ MAX_BUFFER_SIZE = 2**14
19
+
20
+ NEWLINE = "\n"
21
+
22
+ NON_NUMERIC_CONVERSIONS = %i[empty ignore minimum].freeze
23
+
24
+ SPACE = " "
25
+
26
+ # The top position
27
+ #
28
+ # @return [Integer]
29
+ #
30
+ # @api public
31
+ attr_reader :top
32
+
33
+ # The left position
34
+ #
35
+ # @return [Integer]
36
+ #
37
+ # @api public
38
+ attr_reader :left
39
+
40
+ # The chart maximum width in terminal columns
41
+ #
42
+ # @return [Integer]
43
+ #
44
+ # @api public
45
+ attr_reader :width
46
+
47
+ # The chart height in terminal lines
48
+ #
49
+ # @return [Integer]
50
+ #
51
+ # @api public
52
+ attr_reader :height
53
+
54
+ # The drawing cursor
55
+ #
56
+ # @return [TTY::Cursor]
57
+ #
58
+ # @api public
59
+ attr_reader :cursor
60
+
61
+ # The custom minimum value used for scaling bars
62
+ #
63
+ # @return [Numeric]
64
+ #
65
+ # @api public
66
+ attr_accessor :min
67
+
68
+ # The custom maximum value used for scaling bars
69
+ #
70
+ # @return [Numeric]
71
+ #
72
+ # @api public
73
+ attr_accessor :max
74
+
75
+ # Create a Sparkline instance
76
+ #
77
+ # @param [Array<Numeric>] data
78
+ # the data to chart
79
+ # @param [Integer] top
80
+ # the top position
81
+ # @param [Integer] left
82
+ # the left position
83
+ # @param [Integer] height
84
+ # the height in terminal lines
85
+ # @param [Integer] width
86
+ # the maximum width in terminal columns
87
+ # @param [Numeric] min
88
+ # the custom minimum value
89
+ # @param [Numeric] max
90
+ # the custom maximum value
91
+ # @param [Array<String>] bars
92
+ # the bars used for display
93
+ # @param [Integer] buffer_size
94
+ # the maximum buffer size
95
+ # @param [Symbol] non_numeric
96
+ # the replacement for a non-numeric value
97
+ #
98
+ # @api public
99
+ def initialize(data = [], top: nil, left: nil, height: 1, width: nil,
100
+ min: nil, max: nil, bars: BARS, buffer_size: MAX_BUFFER_SIZE,
101
+ non_numeric: :empty)
102
+ check_minmax(min, max) if min && max
103
+ check_non_numeric(non_numeric)
104
+
105
+ @data = Array(data).dup
106
+ @cached_data_size = @data.size
107
+ @top = top
108
+ @left = left
109
+ @height = height
110
+ @width = width
111
+ @min = min
112
+ @max = max
113
+ @bars = bars
114
+ @num_of_bars = bars.size
115
+ @buffer_size = buffer_size
116
+ @non_numeric = non_numeric
117
+ @filter = ->(value) { value.is_a?(::Numeric) }
118
+ @cursor = TTY::Cursor
119
+ end
120
+
121
+ # Append value(s)
122
+ #
123
+ # @example
124
+ # sparkline.push(1, 2, 3, 4)
125
+ #
126
+ # @example
127
+ # sparkline << 1 << 2 << 3 << 4
128
+ #
129
+ # @param [Array<Numeric>] nums
130
+ #
131
+ # @return [self]
132
+ #
133
+ # @api public
134
+ def push(*nums)
135
+ @data.push(*nums)
136
+ @cached_data_size += nums.size
137
+
138
+ if (overflow = @cached_data_size - @buffer_size) > 0
139
+ @data.shift(overflow)
140
+ @cached_data_size -= overflow
141
+ end
142
+
143
+ self
144
+ end
145
+ alias append push
146
+ alias << push
147
+
148
+ # The number of values
149
+ #
150
+ # @return [Integer]
151
+ #
152
+ # @api public
153
+ def size
154
+ @cached_data_size
155
+ end
156
+
157
+ # Render data as a sparkline chart
158
+ #
159
+ # @example
160
+ # sparkline.render
161
+ #
162
+ # @param [Integer] min
163
+ # the minimum value to display
164
+ # @param [Integer] max
165
+ # the maximum value to display
166
+ #
167
+ # @return [String]
168
+ # the rendered sparkline chart
169
+ #
170
+ # @api public
171
+ def render(min: nil, max: nil)
172
+ return EMPTY if @data.empty?
173
+
174
+ buffer = []
175
+ calc_min, calc_max = data_minmax(min, max)
176
+ check_minmax(calc_min, calc_max)
177
+
178
+ height.times do |y|
179
+ buffer << position(y) if position?
180
+ @data[data_range].each.with_index do |value, x|
181
+ bar_index = clamp_and_scale(value, calc_min, calc_max)
182
+ bar = convert_to_bar(bar_index, height - 1 - y)
183
+ bar = yield(value, bar, x, y) if block_given?
184
+ buffer << bar
185
+ end
186
+ buffer << NEWLINE unless y == height - 1
187
+ end
188
+
189
+ buffer.join
190
+ end
191
+
192
+ private
193
+
194
+ # Find the minimum and maximum value in the data
195
+ #
196
+ # @param [Integer] min
197
+ # the custom minimum value
198
+ # @param [Integer] max
199
+ # the custom maximum value
200
+ #
201
+ # @return [Array<Numeric, Numeric>]
202
+ #
203
+ # @api private
204
+ def data_minmax(min, max)
205
+ calc_min, calc_max = @data.select(&@filter).minmax
206
+ [min || @min || calc_min, max || @max || calc_max]
207
+ end
208
+
209
+ # Check maximum isn't less than minimum
210
+ #
211
+ # @raise [Error]
212
+ #
213
+ # @api private
214
+ def check_minmax(min, max)
215
+ return if min <= max
216
+
217
+ raise Error, "maximum value cannot be less than minimum"
218
+ end
219
+
220
+ # Check whether non_numeric has a valid conversion type
221
+ #
222
+ # @param [Symbol] type
223
+ # the type of conversion
224
+ #
225
+ # @raise [Error]
226
+ #
227
+ # @api private
228
+ def check_non_numeric(type)
229
+ return if NON_NUMERIC_CONVERSIONS.include?(type)
230
+
231
+ raise Error, "unknown non_numeric value: #{type.inspect}"
232
+ end
233
+
234
+ # Check whether or not to position this chart
235
+ #
236
+ # @return [Boolean]
237
+ #
238
+ # @api private
239
+ def position?
240
+ top || left
241
+ end
242
+
243
+ # Find a position at which to display this chart
244
+ #
245
+ # @return [String]
246
+ #
247
+ # @api private
248
+ def position(offset = 0)
249
+ if left && top
250
+ cursor.move_to(left, top + offset)
251
+ elsif left
252
+ cursor.column(left)
253
+ elsif top
254
+ cursor.row(top + offset)
255
+ end
256
+ end
257
+
258
+ # Find a range of data values matching width
259
+ #
260
+ # @return [Range]
261
+ #
262
+ # @api private
263
+ def data_range
264
+ start_index = 0
265
+ if width && width < @cached_data_size
266
+ start_index = @cached_data_size - width
267
+ end
268
+ start_index..-1
269
+ end
270
+
271
+ # Clamp value and scale it against height and number of bars
272
+ #
273
+ # @param [Object] value
274
+ # the value to clamp and scale
275
+ # @param [Integer] min
276
+ # the minimum value
277
+ # @param [Integer] max
278
+ # the maximum value
279
+ #
280
+ # @return [Integer]
281
+ #
282
+ # @api private
283
+ def clamp_and_scale(value, min, max)
284
+ return value unless value.is_a?(Numeric)
285
+
286
+ clamped_value = value > max ? max : (value < min ? min : value)
287
+ reduced_value = max == min ? clamped_value : clamped_value - min
288
+ reduced_max = max == min ? (max.zero? ? 1 : max) : max - min
289
+ reduced_value * height * (@num_of_bars - 1) / reduced_max
290
+ end
291
+
292
+ # Convert an index to a bar representation
293
+ #
294
+ # @param [Integer] bar_index
295
+ # the bar index within bars
296
+ # @param [Integer] offset
297
+ # the offset from the bottom
298
+ #
299
+ # @return [String]
300
+ # the rendered bar
301
+ #
302
+ # @api private
303
+ def convert_to_bar(bar_index, offset)
304
+ return convert_non_numeric unless bar_index.is_a?(Numeric)
305
+
306
+ if bar_index >= offset * @num_of_bars
307
+ bar_index -= offset * @num_of_bars
308
+ @bars[bar_index >= @num_of_bars ? -1 : bar_index]
309
+ else
310
+ SPACE
311
+ end
312
+ end
313
+
314
+ # Convert non-numeric value into display string
315
+ #
316
+ # @return [String]
317
+ #
318
+ # @api private
319
+ def convert_non_numeric
320
+ case @non_numeric
321
+ when :empty
322
+ SPACE
323
+ when :ignore
324
+ EMPTY
325
+ when :minimum
326
+ @bars[0]
327
+ end
328
+ end
329
+ end # Sparkline
330
+ end # TTY
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TTY
4
+ class Sparkline
5
+ VERSION = "0.1.0"
6
+ end # Sparkline
7
+ end # TTY
metadata ADDED
@@ -0,0 +1,100 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: tty-sparkline
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Piotr Murach
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2021-05-20 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: tty-cursor
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0.7'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '0.7'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '3.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
55
+ description: Sparkline charts for terminal applications.
56
+ email:
57
+ - piotr@piotrmurach.com
58
+ executables: []
59
+ extensions: []
60
+ extra_rdoc_files:
61
+ - README.md
62
+ - CHANGELOG.md
63
+ - LICENSE.txt
64
+ files:
65
+ - CHANGELOG.md
66
+ - LICENSE.txt
67
+ - README.md
68
+ - lib/tty-sparkline.rb
69
+ - lib/tty/sparkline.rb
70
+ - lib/tty/sparkline/version.rb
71
+ homepage: https://ttytoolkit.org
72
+ licenses:
73
+ - MIT
74
+ metadata:
75
+ allowed_push_host: https://rubygems.org
76
+ bug_tracker_uri: https://github.com/piotrmurach/tty-sparkline/issues
77
+ changelog_uri: https://github.com/piotrmurach/tty-sparkline/blob/master/CHANGELOG.md
78
+ documentation_uri: https://www.rubydoc.info/gems/tty-sparkline
79
+ homepage_uri: https://ttytoolkit.org
80
+ source_code_uri: https://github.com/piotrmurach/tty-sparkline
81
+ post_install_message:
82
+ rdoc_options: []
83
+ require_paths:
84
+ - lib
85
+ required_ruby_version: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: 2.0.0
90
+ required_rubygems_version: !ruby/object:Gem::Requirement
91
+ requirements:
92
+ - - ">="
93
+ - !ruby/object:Gem::Version
94
+ version: '0'
95
+ requirements: []
96
+ rubygems_version: 3.1.2
97
+ signing_key:
98
+ specification_version: 4
99
+ summary: Sparkline charts for terminal applications.
100
+ test_files: []