ratatui_ruby 0.7.2 → 0.7.3

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.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/.builds/ruby-3.2.yml +1 -1
  3. data/.builds/ruby-3.3.yml +1 -1
  4. data/.builds/ruby-3.4.yml +1 -1
  5. data/.builds/ruby-4.0.0.yml +1 -1
  6. data/AGENTS.md +4 -3
  7. data/CHANGELOG.md +28 -0
  8. data/README.md +2 -2
  9. data/doc/concepts/application_testing.md +4 -2
  10. data/doc/contributors/developing_examples.md +7 -7
  11. data/doc/contributors/upstream_requests/tab_rects.md +173 -0
  12. data/doc/contributors/upstream_requests/title_rects.md +132 -0
  13. data/doc/contributors/v1.0.0_blockers.md +46 -739
  14. data/doc/troubleshooting/tui_output.md +76 -0
  15. data/examples/widget_barchart/README.md +1 -1
  16. data/examples/widget_block/README.md +1 -1
  17. data/examples/widget_overlay/README.md +1 -1
  18. data/ext/ratatui_ruby/Cargo.lock +1 -1
  19. data/ext/ratatui_ruby/Cargo.toml +1 -1
  20. data/ext/ratatui_ruby/src/lib.rs +2 -2
  21. data/ext/ratatui_ruby/src/rendering.rs +9 -0
  22. data/ext/ratatui_ruby/src/style.rs +22 -2
  23. data/ext/ratatui_ruby/src/text.rs +26 -0
  24. data/ext/ratatui_ruby/src/widgets/chart.rs +5 -0
  25. data/lib/ratatui_ruby/style/style.rb +1 -0
  26. data/lib/ratatui_ruby/test_helper/snapshot.rb +60 -21
  27. data/lib/ratatui_ruby/test_helper/snapshots/axis_labels_alignment.ansi +24 -0
  28. data/lib/ratatui_ruby/test_helper/snapshots/axis_labels_alignment.txt +24 -0
  29. data/lib/ratatui_ruby/test_helper/snapshots/barchart_styled_label.ansi +5 -0
  30. data/lib/ratatui_ruby/test_helper/snapshots/barchart_styled_label.txt +5 -0
  31. data/lib/ratatui_ruby/test_helper/snapshots/chart_rendering.ansi +24 -0
  32. data/lib/ratatui_ruby/test_helper/snapshots/chart_rendering.txt +24 -0
  33. data/lib/ratatui_ruby/test_helper/snapshots/half_block_marker.ansi +12 -0
  34. data/lib/ratatui_ruby/test_helper/snapshots/half_block_marker.txt +12 -0
  35. data/lib/ratatui_ruby/test_helper/snapshots/legend_position_bottom.ansi +12 -0
  36. data/lib/ratatui_ruby/test_helper/snapshots/legend_position_bottom.txt +12 -0
  37. data/lib/ratatui_ruby/test_helper/snapshots/legend_position_left.ansi +12 -0
  38. data/lib/ratatui_ruby/test_helper/snapshots/legend_position_left.txt +12 -0
  39. data/lib/ratatui_ruby/test_helper/snapshots/legend_position_right.ansi +12 -0
  40. data/lib/ratatui_ruby/test_helper/snapshots/legend_position_right.txt +12 -0
  41. data/lib/ratatui_ruby/test_helper/snapshots/legend_position_top.ansi +12 -0
  42. data/lib/ratatui_ruby/test_helper/snapshots/legend_position_top.txt +12 -0
  43. data/lib/ratatui_ruby/test_helper/snapshots/my_snapshot.txt +1 -0
  44. data/lib/ratatui_ruby/test_helper/snapshots/styled_axis_title.ansi +10 -0
  45. data/lib/ratatui_ruby/test_helper/snapshots/styled_axis_title.txt +10 -0
  46. data/lib/ratatui_ruby/test_helper/snapshots/styled_dataset_name.ansi +10 -0
  47. data/lib/ratatui_ruby/test_helper/snapshots/styled_dataset_name.txt +10 -0
  48. data/lib/ratatui_ruby/test_helper/terminal.rb +3 -0
  49. data/lib/ratatui_ruby/test_helper.rb +1 -1
  50. data/lib/ratatui_ruby/version.rb +1 -1
  51. data/lib/ratatui_ruby/widgets/bar_chart.rb +3 -2
  52. data/lib/ratatui_ruby/widgets/block.rb +42 -0
  53. data/lib/ratatui_ruby/widgets/chart.rb +9 -4
  54. data/lib/ratatui_ruby/widgets/sparkline.rb +3 -2
  55. data/lib/ratatui_ruby.rb +128 -9
  56. metadata +26 -2
@@ -0,0 +1,76 @@
1
+ <!--
2
+ SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
3
+ SPDX-License-Identifier: CC-BY-SA-4.0
4
+ -->
5
+
6
+ # Terminal Output During TUI Sessions
7
+
8
+ ## The Problem
9
+
10
+ Writing to stdout or stderr during a TUI session **corrupts the display**.
11
+
12
+ When your application is running inside `RatatuiRuby.run`, the terminal is in "raw mode" and RatatuiRuby has taken control of the display buffer. Any output via `puts`, `warn`, `p`, `print`, or direct writes to `STDOUT`/`STDERR` will:
13
+
14
+ 1. **Corrupt the screen layout** - Characters appear in random positions
15
+ 2. **Mix with TUI output** - Text interleaves with your widgets unpredictably
16
+ 3. **Trigger escape sequence errors** - Partial ANSI codes can break rendering
17
+
18
+ ## Why This Happens
19
+
20
+ In raw mode:
21
+ - The terminal doesn't process newlines or carriage returns normally
22
+ - Output bypasses the TUI's controlled buffer
23
+ - Cursor position is undefined from the TUI's perspective
24
+
25
+ ## Safe Patterns
26
+
27
+ ### Defer output until after the TUI exits
28
+
29
+ ```ruby
30
+ messages = []
31
+
32
+ RatatuiRuby.run do |tui|
33
+ # Collect messages instead of printing them
34
+ messages << "Something happened"
35
+
36
+ # ... TUI logic ...
37
+ end
38
+
39
+ # Now safe to print
40
+ messages.each { |msg| puts msg }
41
+ ```
42
+
43
+ ### Use debug logging to a file
44
+
45
+ ```ruby
46
+ DEBUG_LOG = File.open("/tmp/my_app_debug.log", "a")
47
+
48
+ RatatuiRuby.run do |tui|
49
+ DEBUG_LOG.puts "Debug: something happened"
50
+ DEBUG_LOG.flush
51
+
52
+ # ... TUI logic ...
53
+ end
54
+ ```
55
+
56
+ ### Display messages in the TUI itself
57
+
58
+ ```ruby
59
+ RatatuiRuby.run do |tui|
60
+ @status_message = "Something happened"
61
+
62
+ tui.draw do |frame|
63
+ # Show status in the UI
64
+ frame.render_widget(
65
+ tui.paragraph(text: @status_message),
66
+ status_area
67
+ )
68
+ end
69
+ end
70
+ ```
71
+
72
+ ## Library Behavior
73
+
74
+ RatatuiRuby automatically defers its own warnings (like experimental feature notices) during TUI sessions. They are queued and printed after `restore_terminal` is called.
75
+
76
+ You don't need to do anything special for library warnings—they're handled automatically.
@@ -5,7 +5,7 @@ SPDX-License-Identifier: CC-BY-SA-4.0
5
5
 
6
6
  # BarChart (Bar, BarGroup) Example
7
7
 
8
- [![](../../doc/images/widget_barchart.png)](app.rb)
8
+ [![](../../doc/images/widget_barchart.png)](app.rb)
9
9
 
10
10
  Visualizes categorical data with interactive attribute cycling.
11
11
 
@@ -4,7 +4,7 @@
4
4
  -->
5
5
  # Block Example
6
6
 
7
- [![](../../doc/images/widget_block.png)](app.rb)
7
+ [![](../../doc/images/widget_block.png)](app.rb)
8
8
 
9
9
  This example demonstrates the versatile `Block` widget, which provides the visual container, borders, and titles for almost every other widget in `ratatui_ruby`.
10
10
 
@@ -4,7 +4,7 @@
4
4
  -->
5
5
  # Overlay Example
6
6
 
7
- [![](../../doc/images/widget_overlay.png)](app.rb)
7
+ [![](../../doc/images/widget_overlay.png)](app.rb)
8
8
 
9
9
  This example demonstrates the `Overlay` composition pattern for layering widgets with depth. Modals, notifications, and floating panels all require stacking widgets on top of each other.
10
10
 
@@ -1012,7 +1012,7 @@ dependencies = [
1012
1012
 
1013
1013
  [[package]]
1014
1014
  name = "ratatui_ruby"
1015
- version = "0.7.2"
1015
+ version = "0.7.3"
1016
1016
  dependencies = [
1017
1017
  "bumpalo",
1018
1018
  "lazy_static",
@@ -3,7 +3,7 @@
3
3
 
4
4
  [package]
5
5
  name = "ratatui_ruby"
6
- version = "0.7.2"
6
+ version = "0.7.3"
7
7
  edition = "2021"
8
8
 
9
9
  [lib]
@@ -121,7 +121,7 @@ fn init() -> Result<(), Error> {
121
121
  let m = ruby.define_module("RatatuiRuby")?;
122
122
 
123
123
  m.define_module_function("_init_terminal", function!(init_terminal, 2))?;
124
- m.define_module_function("restore_terminal", function!(restore_terminal, 0))?;
124
+ m.define_module_function("_restore_terminal", function!(restore_terminal, 0))?;
125
125
  m.define_module_function("_draw", function!(draw, -1))?;
126
126
 
127
127
  // Register Frame class
@@ -147,7 +147,7 @@ fn init() -> Result<(), Error> {
147
147
 
148
148
  // Test backend helpers
149
149
  m.define_module_function(
150
- "init_test_terminal",
150
+ "_init_test_terminal",
151
151
  function!(terminal::init_test_terminal, 2),
152
152
  )?;
153
153
  m.define_module_function(
@@ -60,6 +60,15 @@ pub fn render_node(frame: &mut Frame, area: Rect, node: Value) -> Result<(), Err
60
60
  "RatatuiRuby::Widgets::RatatuiMascot" => {
61
61
  widgets::ratatui_mascot::render_ratatui_mascot(frame, area, node)?;
62
62
  }
63
+ // Text primitives can also be rendered directly as widgets
64
+ "RatatuiRuby::Text::Line" => {
65
+ let line = crate::text::parse_line(node)?;
66
+ frame.render_widget(line, area);
67
+ }
68
+ "RatatuiRuby::Text::Span" => {
69
+ let span = crate::text::parse_span(node)?;
70
+ frame.render_widget(span, area);
71
+ }
63
72
  _ => {}
64
73
  }
65
74
  Ok(())
@@ -171,8 +171,28 @@ pub fn parse_border_set<'a>(
171
171
 
172
172
  pub fn parse_bar_set<'a>(set_val: Value, bump: &'a Bump) -> Result<symbols::bar::Set<'a>, Error> {
173
173
  let ruby = magnus::Ruby::get().unwrap();
174
- let hash = magnus::RHash::from_value(set_val)
175
- .ok_or_else(|| Error::new(ruby.exception_type_error(), "expected hash for bar_set"))?;
174
+
175
+ // Check if set_val is a symbol shortcut
176
+ if let Ok(sym) = magnus::Symbol::from_value(set_val)
177
+ .ok_or::<Error>(Error::new(ruby.exception_type_error(), ""))
178
+ {
179
+ let sym_str = sym.name().unwrap_or_default();
180
+ return match sym_str.as_ref() {
181
+ "nine_levels" => Ok(symbols::bar::NINE_LEVELS),
182
+ "three_levels" => Ok(symbols::bar::THREE_LEVELS),
183
+ _ => Err(Error::new(
184
+ ruby.exception_arg_error(),
185
+ format!("unknown bar_set symbol: :{sym_str}"),
186
+ )),
187
+ };
188
+ }
189
+
190
+ let hash = magnus::RHash::from_value(set_val).ok_or_else(|| {
191
+ Error::new(
192
+ ruby.exception_type_error(),
193
+ "expected symbol or hash for bar_set",
194
+ )
195
+ })?;
176
196
 
177
197
  let get_char = |key: &str| -> Result<Option<&'a str>, Error> {
178
198
  let mut val: Value = hash
@@ -133,6 +133,8 @@ pub fn parse_line(value: Value) -> Result<Line<'static>, Error> {
133
133
  let spans_val: Value = value.funcall("spans", ())?;
134
134
  // v0.7.0: Extract style from the Ruby Line
135
135
  let style_val: Value = value.funcall("style", ())?;
136
+ // Extract alignment from the Ruby Line
137
+ let alignment_val: Value = value.funcall("alignment", ())?;
136
138
 
137
139
  if spans_val.is_nil() {
138
140
  return Ok(Line::from(""));
@@ -177,6 +179,30 @@ pub fn parse_line(value: Value) -> Result<Line<'static>, Error> {
177
179
  line = line.style(parse_style(style_val)?);
178
180
  }
179
181
 
182
+ // Apply alignment if present
183
+ if !alignment_val.is_nil() {
184
+ if let Ok(alignment_sym) = magnus::Symbol::try_convert(alignment_val) {
185
+ let alignment_str = alignment_sym.name().map_err(|e| {
186
+ Error::new(
187
+ ruby.exception_type_error(),
188
+ format!("Invalid alignment symbol: {e}"),
189
+ )
190
+ })?;
191
+ let alignment = match alignment_str.as_ref() {
192
+ "left" => ratatui::layout::Alignment::Left,
193
+ "center" => ratatui::layout::Alignment::Center,
194
+ "right" => ratatui::layout::Alignment::Right,
195
+ _ => {
196
+ return Err(Error::new(
197
+ ruby.exception_arg_error(),
198
+ format!("Invalid alignment: {alignment_str}. Valid values: :left, :center, :right"),
199
+ ));
200
+ }
201
+ };
202
+ line = line.alignment(alignment);
203
+ }
204
+ }
205
+
180
206
  Ok(line)
181
207
  }
182
208
 
@@ -75,6 +75,7 @@ pub fn render(frame: &mut Frame, area: Rect, node: Value) -> Result<(), Error> {
75
75
  "braille" => symbols::Marker::Braille,
76
76
  "block" => symbols::Marker::Block,
77
77
  "bar" => symbols::Marker::Bar,
78
+ "half_block" => symbols::Marker::HalfBlock,
78
79
  _ => symbols::Marker::Dot,
79
80
  };
80
81
 
@@ -115,8 +116,12 @@ pub fn render(frame: &mut Frame, area: Rect, node: Value) -> Result<(), Error> {
115
116
  let pos_sym: Symbol = legend_position_val.funcall("to_sym", ())?;
116
117
  let pos = match pos_sym.to_string().as_str() {
117
118
  "top_left" => LegendPosition::TopLeft,
119
+ "top" => LegendPosition::Top,
118
120
  "bottom_left" => LegendPosition::BottomLeft,
121
+ "bottom" => LegendPosition::Bottom,
119
122
  "bottom_right" => LegendPosition::BottomRight,
123
+ "left" => LegendPosition::Left,
124
+ "right" => LegendPosition::Right,
120
125
  _ => LegendPosition::TopRight,
121
126
  };
122
127
  chart = chart.legend_position(Some(pos));
@@ -39,6 +39,7 @@ module RatatuiRuby
39
39
  # * <tt>:dark_gray</tt>, <tt>:light_red</tt>, <tt>:light_green</tt>,
40
40
  # <tt>:light_yellow</tt>, <tt>:light_blue</tt>, <tt>:light_magenta</tt>,
41
41
  # <tt>:light_cyan</tt>, <tt>:white</tt>
42
+ # * <tt>:reset</tt> — Restores the terminal's default color.
42
43
  #
43
44
  # ==== String
44
45
  # Represents a specific RGB color using a Hex code (<tt>"#RRGGBB"</tt>).
@@ -15,9 +15,10 @@ module RatatuiRuby
15
15
  # Verifying every character of a TUI screen by hand is tedious. Snapshots let you
16
16
  # capture the screen once and compare against it in future runs.
17
17
  #
18
- # This mixin provides <tt>assert_snapshot</tt> for plain text and
19
- # <tt>assert_rich_snapshot</tt> for styled ANSI output. Both auto-create
20
- # snapshot files on first run.
18
+ # This mixin provides <tt>assert_plain_snapshot</tt> for plain text,
19
+ # <tt>assert_rich_snapshot</tt> for styled ANSI output, and
20
+ # <tt>assert_snapshots</tt> (plural) for both. All auto-create snapshot
21
+ # files on first run.
21
22
  #
22
23
  # Use it to verify complex layouts, styles, and interactions without manual assertions.
23
24
  #
@@ -62,38 +63,46 @@ module RatatuiRuby
62
63
  #
63
64
  # Mask dynamic content (timestamps, IDs) with a normalization block:
64
65
  #
65
- # assert_snapshot("dashboard") do |lines|
66
+ # assert_snapshots("dashboard") do |lines|
66
67
  # lines.map { |l| l.gsub(/\d{4}-\d{2}-\d{2}/, "YYYY-MM-DD") }
67
68
  # end
68
69
  #
69
70
  module Snapshot
70
71
  ##
71
- # Asserts that the current screen content matches a stored snapshot.
72
+ # Asserts that the current screen content matches a stored plain text snapshot.
72
73
  #
73
- # This method simplifies snapshot testing by automatically resolving the snapshot path
74
- # relative to the test file calling this method. It assumes a "snapshots" directory
75
- # exists in the same directory as the test file.
74
+ # Plain text snapshots capture layout but miss styling bugs: wrong colors, missing bold,
75
+ # invisible text on a matching background. *Prefer <tt>assert_snapshots</tt>* (plural) to catch
76
+ # styling regressions.
76
77
  #
77
- # # In test/test_login.rb
78
- # assert_snapshot("login_screen")
79
- # # Look for: test/snapshots/login_screen.txt
78
+ # Plain text snapshots are human-readable when viewed in any editor or diff tool. They
79
+ # pair well with rich snapshots for documentation. Use <tt>assert_snapshots</tt> to generate both.
80
+ #
81
+ # assert_plain_snapshot("login_screen")
82
+ # # Compares against: test/snapshots/login_screen.txt
80
83
  #
81
84
  # # With normalization block
82
- # assert_snapshot("clock") do |actual|
85
+ # assert_plain_snapshot("clock") do |actual|
83
86
  # actual.map { |l| l.gsub(/\d{2}:\d{2}/, "XX:XX") }
84
87
  # end
85
88
  #
86
89
  # [name] String name of the snapshot (without extension).
87
90
  # [msg] String optional failure message.
88
- def assert_snapshot(name, msg = nil, &)
91
+ def assert_plain_snapshot(name, msg = nil, snapshot_dir: nil, &)
89
92
  # Get the path of the test file calling this method
90
- caller_path = caller_locations(1, 1).first.path
91
- snapshot_dir = File.join(File.dirname(caller_path), "snapshots")
93
+ snapshot_dir ||= File.join(File.dirname(caller_locations(1, 1).first.path), "snapshots")
92
94
  snapshot_path = File.join(snapshot_dir, "#{name}.txt")
93
95
 
94
96
  assert_screen_matches(snapshot_path, msg, &)
95
97
  end
96
98
 
99
+ ##
100
+ # @deprecated Use {#assert_plain_snapshot} instead.
101
+ def assert_snapshot(name, msg = nil, &)
102
+ warn "assert_snapshot is deprecated; use assert_plain_snapshot instead", uplevel: 1
103
+ assert_plain_snapshot(name, msg, &)
104
+ end
105
+
97
106
  ##
98
107
  # Asserts that the current screen content matches the expected content.
99
108
  #
@@ -186,12 +195,17 @@ module RatatuiRuby
186
195
  end
187
196
 
188
197
  ##
189
- # Asserts that the current screen content (including colors!) matches a stored ANSI snapshot.
198
+ # Asserts that the current screen content (including colors and styles) matches a stored ANSI snapshot.
199
+ #
200
+ # TUIs communicate meaning through colors and styles. Rich snapshots capture everything:
201
+ # wrong colors, missing bold, invisible text on a matching background. *Prefer <tt>assert_snapshots</tt>*
202
+ # (plural) to also generate human-readable plain text files for documentation.
190
203
  #
191
- # Generates/Compares against a file with <tt>.ansi</tt> extension.
192
- # You can <tt>cat</tt> this file to see exactly what the screen looked like.
204
+ # The <tt>.ansi</tt> snapshot files contain ANSI escape codes. You can <tt>cat</tt> them in a terminal
205
+ # to see exactly what the screen looked like.
193
206
  #
194
207
  # assert_rich_snapshot("login_screen")
208
+ # # Compares against: test/snapshots/login_screen.ansi
195
209
  #
196
210
  # # With normalization
197
211
  # assert_rich_snapshot("log_view") do |lines|
@@ -200,9 +214,8 @@ module RatatuiRuby
200
214
  #
201
215
  # [name] String snapshot name.
202
216
  # [msg] String optional failure message.
203
- def assert_rich_snapshot(name, msg = nil)
204
- caller_path = caller_locations(1, 1).first.path
205
- snapshot_dir = File.join(File.dirname(caller_path), "snapshots")
217
+ def assert_rich_snapshot(name, msg = nil, snapshot_dir: nil)
218
+ snapshot_dir ||= File.join(File.dirname(caller_locations(1, 1).first.path), "snapshots")
206
219
  snapshot_path = File.join(snapshot_dir, "#{name}.ansi")
207
220
 
208
221
  actual_content = _render_buffer_with_ansi
@@ -254,6 +267,32 @@ module RatatuiRuby
254
267
  end
255
268
  end
256
269
 
270
+ ##
271
+ # Asserts both plain text and rich (ANSI-styled) snapshots match.
272
+ #
273
+ # This is the recommended snapshot assertion. It calls both <tt>assert_plain_snapshot</tt> and
274
+ # <tt>assert_rich_snapshot</tt> with the same name, generating <tt>.txt</tt> and <tt>.ansi</tt> files.
275
+ #
276
+ # Rich snapshots catch styling bugs that plain text misses. Plain text snapshots are
277
+ # human-readable in any editor or diff tool, making them valuable for documentation and
278
+ # code review. Together, they provide comprehensive coverage and discoverability.
279
+ #
280
+ # assert_snapshots("login_screen")
281
+ # # Creates/compares: snapshots/login_screen.txt AND snapshots/login_screen.ansi
282
+ #
283
+ # # With normalization (masks dynamic content like timestamps)
284
+ # assert_snapshots("dashboard") do |lines|
285
+ # lines.map { |l| l.gsub(/\d{2}:\d{2}:\d{2}/, "HH:MM:SS") }
286
+ # end
287
+ #
288
+ # [name] String snapshot name (without extension).
289
+ # [msg] String optional failure message.
290
+ def assert_snapshots(name, msg = nil, &)
291
+ snapshot_dir = File.join(File.dirname(caller_locations(1, 1).first.path), "snapshots")
292
+ assert_plain_snapshot(name, msg, snapshot_dir:, &)
293
+ assert_rich_snapshot(name, msg, snapshot_dir:, &)
294
+ end
295
+
257
296
  ##
258
297
  # Returns the current buffer content as an ANSI-encoded string.
259
298
  #
@@ -0,0 +1,24 @@
1
+ ┌Aligned Chart─────────────────────────────────────────────────────────────────┐
2
+ │10│Value ┌──────┐│
3
+ │ │ │TestDS││
4
+ │ │ ••└──────┘│
5
+ │ │ •••• │
6
+ │ │ •••• │
7
+ │ │ •••• │
8
+ │ │ •••• │
9
+ │ │ •••• │
10
+ │ │ •••• │
11
+ │ │ ••• │
12
+ │ 5│ •••• │
13
+ │ │ •••• │
14
+ │ │ •••• │
15
+ │ │ •••• │
16
+ │ │ •••• │
17
+ │ │ •••• │
18
+ │ │ •••• │
19
+ │ │ •••• │
20
+ │ │ •••• │
21
+ │ 0│•• Time│
22
+ │ └───────────────────────────────────────────────────────────────────────────│
23
+ │ 0 5 10│
24
+ └──────────────────────────────────────────────────────────────────────────────┘
@@ -0,0 +1,24 @@
1
+ ┌Aligned Chart─────────────────────────────────────────────────────────────────┐
2
+ │10│Value ┌──────┐│
3
+ │ │ │TestDS││
4
+ │ │ ••└──────┘│
5
+ │ │ •••• │
6
+ │ │ •••• │
7
+ │ │ •••• │
8
+ │ │ •••• │
9
+ │ │ •••• │
10
+ │ │ •••• │
11
+ │ │ ••• │
12
+ │ 5│ •••• │
13
+ │ │ •••• │
14
+ │ │ •••• │
15
+ │ │ •••• │
16
+ │ │ •••• │
17
+ │ │ •••• │
18
+ │ │ •••• │
19
+ │ │ •••• │
20
+ │ │ •••• │
21
+ │ 0│•• Time│
22
+ │ └───────────────────────────────────────────────────────────────────────────│
23
+ │ 0 5 10│
24
+ └──────────────────────────────────────────────────────────────────────────────┘
@@ -0,0 +1,5 @@
1
+ █████ 
2
+ █████ 
3
+ █████ 
4
+ ██5██ 
5
+ Group 
@@ -0,0 +1,5 @@
1
+ █████
2
+ █████
3
+ █████
4
+ ██5██
5
+ Group
@@ -0,0 +1,24 @@
1
+ ┌Test Chart────────────────────────────────────────────────────────────────────┐
2
+ │10│Value ┌──────┐│
3
+ │ │ │TestDS││
4
+ │ │ ••└──────┘│
5
+ │ │ •••• │
6
+ │ │ •••• │
7
+ │ │ •••• │
8
+ │ │ •••• │
9
+ │ │ •••• │
10
+ │ │ •••• │
11
+ │ │ ••• │
12
+ │ │ •••• │
13
+ │ │ •••• │
14
+ │ │ •••• │
15
+ │ │ •••• │
16
+ │ │ •••• │
17
+ │ │ •••• │
18
+ │ │ •••• │
19
+ │ │ •••• │
20
+ │ │ •••• │
21
+ │0 │•• Time│
22
+ │ └───────────────────────────────────────────────────────────────────────────│
23
+ │ 0 10│
24
+ └──────────────────────────────────────────────────────────────────────────────┘
@@ -0,0 +1,24 @@
1
+ ┌Test Chart────────────────────────────────────────────────────────────────────┐
2
+ │10│Value ┌──────┐│
3
+ │ │ │TestDS││
4
+ │ │ ••└──────┘│
5
+ │ │ •••• │
6
+ │ │ •••• │
7
+ │ │ •••• │
8
+ │ │ •••• │
9
+ │ │ •••• │
10
+ │ │ •••• │
11
+ │ │ ••• │
12
+ │ │ •••• │
13
+ │ │ •••• │
14
+ │ │ •••• │
15
+ │ │ •••• │
16
+ │ │ •••• │
17
+ │ │ •••• │
18
+ │ │ •••• │
19
+ │ │ •••• │
20
+ │ │ •••• │
21
+ │0 │•• Time│
22
+ │ └───────────────────────────────────────────────────────────────────────────│
23
+ │ 0 10│
24
+ └──────────────────────────────────────────────────────────────────────────────┘
@@ -0,0 +1,12 @@
1
+  ┌──┐
2
+  ▄│HB│
3
+  ▄▀ └──┘
4
+  ▄▀▀ 
5
+  ▄▀ 
6
+  ▄▀▀ 
7
+  ▄▄▀ 
8
+  ▄▀ 
9
+  ▄▄▀ 
10
+  ▄▀ 
11
+  ▄▄▀ 
12
+ ▄▀ 
@@ -0,0 +1,12 @@
1
+ ┌──┐
2
+ ▄│HB│
3
+ ▄▀ └──┘
4
+ ▄▀▀
5
+ ▄▀
6
+ ▄▀▀
7
+ ▄▄▀
8
+ ▄▀
9
+ ▄▄▀
10
+ ▄▀
11
+ ▄▄▀
12
+ ▄▀
@@ -0,0 +1,12 @@
1
+  ••
2
+  •• 
3
+  ••• 
4
+  ••• 
5
+  •• 
6
+  ••• 
7
+  ••• 
8
+  •• 
9
+  ••• 
10
+  ••• ┌──┐ 
11
+  •• │DS│ 
12
+ •• └──┘ 
@@ -0,0 +1,12 @@
1
+ ••
2
+ ••
3
+ •••
4
+ •••
5
+ ••
6
+ •••
7
+ •••
8
+ ••
9
+ •••
10
+ ••• ┌──┐
11
+ •• │DS│
12
+ •• └──┘
@@ -0,0 +1,12 @@
1
+  ••
2
+  •• 
3
+  ••• 
4
+  ••• 
5
+ ┌──┐ •• 
6
+ │DS│ ••• 
7
+ └──┘ ••• 
8
+  •• 
9
+  ••• 
10
+  ••• 
11
+  •• 
12
+ •• 
@@ -0,0 +1,12 @@
1
+ ••
2
+ ••
3
+ •••
4
+ •••
5
+ ┌──┐ ••
6
+ │DS│ •••
7
+ └──┘ •••
8
+ ••
9
+ •••
10
+ •••
11
+ ••
12
+ ••
@@ -0,0 +1,12 @@
1
+  ••
2
+  •• 
3
+  ••• 
4
+  ••• 
5
+  •• ┌──┐
6
+  ••• │DS│
7
+  ••• └──┘
8
+  •• 
9
+  ••• 
10
+  ••• 
11
+  •• 
12
+ •• 
@@ -0,0 +1,12 @@
1
+ ••
2
+ ••
3
+ •••
4
+ •••
5
+ •• ┌──┐
6
+ ••• │DS│
7
+ ••• └──┘
8
+ ••
9
+ •••
10
+ •••
11
+ ••
12
+ ••