termchart 0.1.2 → 0.2.0
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 +4 -4
- data/lib/termchart/bar.rb +5 -12
- data/lib/termchart/candle.rb +11 -22
- data/lib/termchart/line.rb +7 -26
- data/lib/termchart/spark.rb +1 -20
- data/lib/termchart/version.rb +1 -1
- data/lib/termchart.rb +32 -0
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 55f452dc8d64878667f8f2b1713082ffa6749c457094c35ee66e0b0defa622b5
|
|
4
|
+
data.tar.gz: b45f48358e95976f342890386a470eed2f9765ec2e6d06a43ebbe4345bf6b1c2
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4c416cae0fdeeec726b42661b071ed3d1e53b87631c69005018c65d865954446b5698eb9c48abd4d7c8d64861738d63eee2e661536c9c67979810bd92e74ca88
|
|
7
|
+
data.tar.gz: bdb901634be7a9ee63e975d3d21460a279b4e3e3e839eebdac060ceffde4d2d1d971d1e98ca42b88576bf13a0b6172be41670e1e6d8f827603d4ff0fab33e326
|
data/lib/termchart/bar.rb
CHANGED
|
@@ -21,6 +21,9 @@ module Termchart
|
|
|
21
21
|
|
|
22
22
|
def render
|
|
23
23
|
return "" if @items.empty?
|
|
24
|
+
if @items.any? { |i| i[:value] < 0 }
|
|
25
|
+
raise ArgumentError, "Bar chart does not support negative values"
|
|
26
|
+
end
|
|
24
27
|
@orientation == :horizontal ? render_horizontal : render_vertical
|
|
25
28
|
end
|
|
26
29
|
|
|
@@ -43,7 +46,7 @@ module Termchart
|
|
|
43
46
|
pad = bar_w - visible_len(bar)
|
|
44
47
|
pad = 0 if pad < 0
|
|
45
48
|
bar_str = bar + " " * pad
|
|
46
|
-
bar_str = colorize(bar_str, item[:color]) if item[:color]
|
|
49
|
+
bar_str = Termchart.colorize(bar_str, item[:color]) if item[:color]
|
|
47
50
|
lbl = item[:label].ljust(label_w)
|
|
48
51
|
val = format_val(item[:value]).rjust(val_w)
|
|
49
52
|
"#{lbl} │#{bar_str} #{val}"
|
|
@@ -78,7 +81,7 @@ module Termchart
|
|
|
78
81
|
h.times do |row|
|
|
79
82
|
parts = cols.each_with_index.map do |col, i|
|
|
80
83
|
cell = col[row]
|
|
81
|
-
@items[i][:color] ? colorize(cell, @items[i][:color]) : cell
|
|
84
|
+
@items[i][:color] ? Termchart.colorize(cell, @items[i][:color]) : cell
|
|
82
85
|
end
|
|
83
86
|
lines << parts.join(" ")
|
|
84
87
|
end
|
|
@@ -96,15 +99,5 @@ module Termchart
|
|
|
96
99
|
str.gsub(/\e\[[0-9;]*m/, "").length
|
|
97
100
|
end
|
|
98
101
|
|
|
99
|
-
def colorize(str, color)
|
|
100
|
-
code = case color
|
|
101
|
-
when Integer then "38;5;#{color}"
|
|
102
|
-
when /\A#?([0-9a-fA-F]{6})\z/
|
|
103
|
-
r, g, b = [$1].pack("H*").unpack("CCC")
|
|
104
|
-
"38;2;#{r};#{g};#{b}"
|
|
105
|
-
else "38;5;#{color}"
|
|
106
|
-
end
|
|
107
|
-
"\e[#{code}m#{str}\e[0m"
|
|
108
|
-
end
|
|
109
102
|
end
|
|
110
103
|
end
|
data/lib/termchart/candle.rb
CHANGED
|
@@ -17,15 +17,15 @@ module Termchart
|
|
|
17
17
|
# Add OHLC data: array of hashes with keys :o, :h, :l, :c
|
|
18
18
|
def add(ohlc_data)
|
|
19
19
|
ohlc_data.each do |d|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
}
|
|
20
|
+
o, h, l, c = d[:o].to_f, d[:h].to_f, d[:l].to_f, d[:c].to_f
|
|
21
|
+
raise ArgumentError, "high (#{h}) must be >= low (#{l})" if h < l
|
|
22
|
+
@data << { o: o, h: h, l: l, c: c }
|
|
24
23
|
end
|
|
25
24
|
end
|
|
26
25
|
|
|
27
26
|
def render
|
|
28
27
|
return "" if @data.empty?
|
|
28
|
+
effective_height = [@height, 3].max
|
|
29
29
|
|
|
30
30
|
data_min = @data.map { |d| d[:l] }.min
|
|
31
31
|
data_max = @data.map { |d| d[:h] }.max
|
|
@@ -33,10 +33,10 @@ module Termchart
|
|
|
33
33
|
data_range = 1.0 if data_range.zero?
|
|
34
34
|
|
|
35
35
|
# Y-axis label width
|
|
36
|
-
y_label_w = [format_num(data_max).length, format_num(data_min).length].max + 1
|
|
36
|
+
y_label_w = [Termchart.format_num(data_max).length, Termchart.format_num(data_min).length].max + 1
|
|
37
37
|
chart_w = @width - y_label_w
|
|
38
38
|
chart_w = 10 if chart_w < 10
|
|
39
|
-
chart_h =
|
|
39
|
+
chart_h = effective_height
|
|
40
40
|
|
|
41
41
|
# Decide how many candles to show (1 char width each, with gaps)
|
|
42
42
|
max_candles = chart_w / 2 # each candle takes 1 col + 1 gap
|
|
@@ -89,18 +89,18 @@ module Termchart
|
|
|
89
89
|
# Add Y-axis labels
|
|
90
90
|
result = lines.each_with_index.map do |line, row|
|
|
91
91
|
if row == 0
|
|
92
|
-
label = format_num(data_max).rjust(y_label_w - 1) + "┤"
|
|
92
|
+
label = Termchart.format_num(data_max).rjust(y_label_w - 1) + "┤"
|
|
93
93
|
elsif row == chart_h - 1
|
|
94
|
-
label = format_num(data_min).rjust(y_label_w - 1) + "┤"
|
|
94
|
+
label = Termchart.format_num(data_min).rjust(y_label_w - 1) + "┤"
|
|
95
95
|
elsif row == chart_h / 2
|
|
96
96
|
mid = data_min + data_range / 2.0
|
|
97
|
-
label = format_num(mid).rjust(y_label_w - 1) + "┤"
|
|
97
|
+
label = Termchart.format_num(mid).rjust(y_label_w - 1) + "┤"
|
|
98
98
|
elsif row == chart_h / 4
|
|
99
99
|
q1 = data_max - data_range / 4.0
|
|
100
|
-
label = format_num(q1).rjust(y_label_w - 1) + "┤"
|
|
100
|
+
label = Termchart.format_num(q1).rjust(y_label_w - 1) + "┤"
|
|
101
101
|
elsif row == chart_h * 3 / 4
|
|
102
102
|
q3 = data_min + data_range / 4.0
|
|
103
|
-
label = format_num(q3).rjust(y_label_w - 1) + "┤"
|
|
103
|
+
label = Termchart.format_num(q3).rjust(y_label_w - 1) + "┤"
|
|
104
104
|
else
|
|
105
105
|
label = " " * (y_label_w - 1) + "│"
|
|
106
106
|
end
|
|
@@ -110,16 +110,5 @@ module Termchart
|
|
|
110
110
|
result.join("\n")
|
|
111
111
|
end
|
|
112
112
|
|
|
113
|
-
private
|
|
114
|
-
|
|
115
|
-
def format_num(v)
|
|
116
|
-
if v.abs >= 1000
|
|
117
|
-
"%.0f" % v
|
|
118
|
-
elsif v.abs >= 100
|
|
119
|
-
"%.1f" % v
|
|
120
|
-
else
|
|
121
|
-
"%.2f" % v
|
|
122
|
-
end
|
|
123
|
-
end
|
|
124
113
|
end
|
|
125
114
|
end
|
data/lib/termchart/line.rb
CHANGED
|
@@ -17,6 +17,7 @@ module Termchart
|
|
|
17
17
|
|
|
18
18
|
def render
|
|
19
19
|
return "" if @series.empty?
|
|
20
|
+
effective_height = [@height, 3].max
|
|
20
21
|
|
|
21
22
|
# Compute global min/max across all series
|
|
22
23
|
all_vals = @series.flat_map { |s| s[:values] }
|
|
@@ -27,10 +28,10 @@ module Termchart
|
|
|
27
28
|
|
|
28
29
|
# Reserve space for Y-axis labels (use widest of min/mid/max)
|
|
29
30
|
mid = data_min + data_range / 2.0
|
|
30
|
-
y_label_w = [format_num(data_max), format_num(data_min), format_num(mid)].map(&:length).max + 1
|
|
31
|
+
y_label_w = [Termchart.format_num(data_max), Termchart.format_num(data_min), Termchart.format_num(mid)].map(&:length).max + 1
|
|
31
32
|
chart_w = @width - y_label_w
|
|
32
33
|
chart_w = 10 if chart_w < 10
|
|
33
|
-
chart_h =
|
|
34
|
+
chart_h = effective_height
|
|
34
35
|
|
|
35
36
|
# Pixel dimensions (braille: 2 dots wide, 4 dots tall per cell)
|
|
36
37
|
px_w = chart_w * 2
|
|
@@ -66,12 +67,12 @@ module Termchart
|
|
|
66
67
|
lines = chart_str.split("\n")
|
|
67
68
|
result = lines.each_with_index.map do |line, row|
|
|
68
69
|
if row == 0
|
|
69
|
-
label = format_num(data_max).rjust(y_label_w - 1) + "┤"
|
|
70
|
+
label = Termchart.format_num(data_max).rjust(y_label_w - 1) + "┤"
|
|
70
71
|
elsif row == chart_h - 1
|
|
71
|
-
label = format_num(data_min).rjust(y_label_w - 1) + "┤"
|
|
72
|
+
label = Termchart.format_num(data_min).rjust(y_label_w - 1) + "┤"
|
|
72
73
|
elsif row == chart_h / 2
|
|
73
74
|
mid = data_min + data_range / 2.0
|
|
74
|
-
label = format_num(mid).rjust(y_label_w - 1) + "┤"
|
|
75
|
+
label = Termchart.format_num(mid).rjust(y_label_w - 1) + "┤"
|
|
75
76
|
else
|
|
76
77
|
label = " " * (y_label_w - 1) + "│"
|
|
77
78
|
end
|
|
@@ -82,7 +83,7 @@ module Termchart
|
|
|
82
83
|
if @series.any? { |s| s[:label] }
|
|
83
84
|
legend = @series.map do |s|
|
|
84
85
|
name = s[:label] || "data"
|
|
85
|
-
colorize("━━", s[:color]) + " " + name
|
|
86
|
+
Termchart.colorize("━━", s[:color]) + " " + name
|
|
86
87
|
end.join(" ")
|
|
87
88
|
result << " " * y_label_w + legend
|
|
88
89
|
end
|
|
@@ -113,25 +114,5 @@ module Termchart
|
|
|
113
114
|
end
|
|
114
115
|
end
|
|
115
116
|
|
|
116
|
-
def format_num(v)
|
|
117
|
-
if v.abs >= 1000
|
|
118
|
-
"%.0f" % v
|
|
119
|
-
elsif v.abs >= 1
|
|
120
|
-
"%.1f" % v
|
|
121
|
-
else
|
|
122
|
-
"%.2f" % v
|
|
123
|
-
end
|
|
124
|
-
end
|
|
125
|
-
|
|
126
|
-
def colorize(str, color)
|
|
127
|
-
code = case color
|
|
128
|
-
when Integer then "38;5;#{color}"
|
|
129
|
-
when /\A#?([0-9a-fA-F]{6})\z/
|
|
130
|
-
r, g, b = [$1].pack("H*").unpack("CCC")
|
|
131
|
-
"38;2;#{r};#{g};#{b}"
|
|
132
|
-
else "38;5;#{color}"
|
|
133
|
-
end
|
|
134
|
-
"\e[#{code}m#{str}\e[0m"
|
|
135
|
-
end
|
|
136
117
|
end
|
|
137
118
|
end
|
data/lib/termchart/spark.rb
CHANGED
|
@@ -15,26 +15,7 @@ module Termchart
|
|
|
15
15
|
TICKS[idx]
|
|
16
16
|
end
|
|
17
17
|
line = chars.join
|
|
18
|
-
color ? colorize(line, color) : line
|
|
18
|
+
color ? Termchart.colorize(line, color) : line
|
|
19
19
|
end
|
|
20
|
-
|
|
21
|
-
private
|
|
22
|
-
|
|
23
|
-
def self.colorize(str, color)
|
|
24
|
-
code = case color
|
|
25
|
-
when Integer then "38;5;#{color}"
|
|
26
|
-
when Symbol then "38;5;#{NAMED_COLORS[color] || 7}"
|
|
27
|
-
when /\A#?([0-9a-fA-F]{6})\z/
|
|
28
|
-
r, g, b = [$1].pack("H*").unpack("CCC")
|
|
29
|
-
"38;2;#{r};#{g};#{b}"
|
|
30
|
-
else "38;5;#{color}"
|
|
31
|
-
end
|
|
32
|
-
"\e[#{code}m#{str}\e[0m"
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
NAMED_COLORS = {
|
|
36
|
-
red: 196, green: 82, blue: 33, yellow: 226, cyan: 51,
|
|
37
|
-
magenta: 201, white: 255, gray: 245, orange: 208
|
|
38
|
-
}.freeze
|
|
39
20
|
end
|
|
40
21
|
end
|
data/lib/termchart/version.rb
CHANGED
data/lib/termchart.rb
CHANGED
|
@@ -6,6 +6,38 @@ require_relative "termchart/candle"
|
|
|
6
6
|
require_relative "termchart/bar"
|
|
7
7
|
|
|
8
8
|
module Termchart
|
|
9
|
+
NAMED_COLORS = {
|
|
10
|
+
red: 196, green: 82, blue: 33, yellow: 226, cyan: 51,
|
|
11
|
+
magenta: 201, white: 255, gray: 245, orange: 208
|
|
12
|
+
}.freeze
|
|
13
|
+
|
|
14
|
+
# Shared colorize helper (ANSI escape wrapping).
|
|
15
|
+
# Supports Integer (256-color), Symbol (named), and hex string.
|
|
16
|
+
def self.colorize(str, color)
|
|
17
|
+
code = case color
|
|
18
|
+
when Integer then "38;5;#{color}"
|
|
19
|
+
when Symbol then "38;5;#{NAMED_COLORS[color] || 7}"
|
|
20
|
+
when /\A#?([0-9a-fA-F]{6})\z/
|
|
21
|
+
r, g, b = [$1].pack("H*").unpack("CCC")
|
|
22
|
+
"38;2;#{r};#{g};#{b}"
|
|
23
|
+
else "38;5;#{color}"
|
|
24
|
+
end
|
|
25
|
+
"\e[#{code}m#{str}\e[0m"
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Shared numeric formatter for axis labels.
|
|
29
|
+
def self.format_num(v)
|
|
30
|
+
if v.abs >= 1000
|
|
31
|
+
"%.0f" % v
|
|
32
|
+
elsif v.abs >= 100
|
|
33
|
+
"%.1f" % v
|
|
34
|
+
elsif v.abs >= 1
|
|
35
|
+
"%.1f" % v
|
|
36
|
+
else
|
|
37
|
+
"%.2f" % v
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
9
41
|
# Convenience: Termchart.spark([1,3,5,2,8], color: :green)
|
|
10
42
|
def self.spark(values, color: nil)
|
|
11
43
|
Spark.render(values, color: color)
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: termchart
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Geir Isene
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-03-
|
|
11
|
+
date: 2026-03-21 00:00:00.000000000 Z
|
|
12
12
|
dependencies: []
|
|
13
13
|
description: Render sparklines, line charts (braille), candlestick charts, and bar
|
|
14
14
|
charts as plain strings with ANSI color codes. Zero dependencies, pure Ruby.
|