tessella 0.3.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 +7 -0
- data/LICENSE +21 -0
- data/README.md +900 -0
- data/doc/README.md +74 -0
- data/doc/bitfield.md +123 -0
- data/doc/charts.md +1370 -0
- data/doc/check.rb +75 -0
- data/doc/cli.md +483 -0
- data/doc/composition.md +599 -0
- data/doc/getting-started.md +121 -0
- data/doc/glyphs.md +134 -0
- data/doc/images.md +325 -0
- data/doc/library.md +239 -0
- data/doc/lint.md +270 -0
- data/doc/meter.md +210 -0
- data/doc/rule.md +51 -0
- data/doc/seq.md +169 -0
- data/doc/sparkline.md +63 -0
- data/doc/table.md +347 -0
- data/doc/targets.md +222 -0
- data/doc/text.md +84 -0
- data/doc/tree.md +108 -0
- data/doc/wave.md +122 -0
- data/examples/README.md +103 -0
- data/examples/ages.txt +9 -0
- data/examples/bad.csv +3 -0
- data/examples/boxes.txt +3 -0
- data/examples/broken.txt +4 -0
- data/examples/calibration.rb +12 -0
- data/examples/cities.csv +4 -0
- data/examples/cjk.rb +13 -0
- data/examples/comment.rb +9 -0
- data/examples/comment.sh +13 -0
- data/examples/cpu.txt +60 -0
- data/examples/dashboard.sh +25 -0
- data/examples/datasheet.rb +16 -0
- data/examples/disc.pgm +0 -0
- data/examples/disks.sh +18 -0
- data/examples/disks.txt +5 -0
- data/examples/dose.txt +7 -0
- data/examples/drift.txt +4 -0
- data/examples/ethernet.txt +3 -0
- data/examples/figure.c +5 -0
- data/examples/fit.rb +16 -0
- data/examples/floors.rb +21 -0
- data/examples/gallery.sh +20 -0
- data/examples/gauges.sh +31 -0
- data/examples/gradient.rb +16 -0
- data/examples/growth.txt +6 -0
- data/examples/hosts.csv +4 -0
- data/examples/icmp.txt +3 -0
- data/examples/ipv4.txt +12 -0
- data/examples/kinds.csv +4 -0
- data/examples/langs.csv +4 -0
- data/examples/lint.rb +16 -0
- data/examples/load.rb +14 -0
- data/examples/login.txt +10 -0
- data/examples/neg.csv +4 -0
- data/examples/normal.txt +200 -0
- data/examples/note.txt +2 -0
- data/examples/notes-fixed.md +17 -0
- data/examples/notes.md +17 -0
- data/examples/ohlc.txt +7 -0
- data/examples/os.csv +10 -0
- data/examples/outline.txt +8 -0
- data/examples/plot.txt +5 -0
- data/examples/refusals.rb +17 -0
- data/examples/report.sh +21 -0
- data/examples/rfc.sh +22 -0
- data/examples/samples.txt +7 -0
- data/examples/shapes.pgm +0 -0
- data/examples/sizes.tsv +3 -0
- data/examples/spi.txt +6 -0
- data/examples/status.sh +23 -0
- data/examples/stem.txt +12 -0
- data/examples/table-drift.txt +4 -0
- data/examples/temps.txt +31 -0
- data/examples/trig.sh +13 -0
- data/examples/trig.txt +102 -0
- data/examples/week.txt +8 -0
- data/examples/wide.txt +2 -0
- data/exe/tessella +6 -0
- data/lib/tessella/axis.rb +71 -0
- data/lib/tessella/bitfield.rb +194 -0
- data/lib/tessella/block.rb +56 -0
- data/lib/tessella/borders.rb +85 -0
- data/lib/tessella/canvas/area.rb +155 -0
- data/lib/tessella/canvas/ascii.rb +120 -0
- data/lib/tessella/canvas/block.rb +40 -0
- data/lib/tessella/canvas/braille.rb +30 -0
- data/lib/tessella/canvas/clip.rb +49 -0
- data/lib/tessella/canvas/density.rb +51 -0
- data/lib/tessella/canvas/dot.rb +27 -0
- data/lib/tessella/canvas/eighths.rb +43 -0
- data/lib/tessella/canvas/half.rb +51 -0
- data/lib/tessella/canvas/sextant.rb +36 -0
- data/lib/tessella/canvas.rb +168 -0
- data/lib/tessella/cell.rb +10 -0
- data/lib/tessella/chart.rb +211 -0
- data/lib/tessella/check.rb +52 -0
- data/lib/tessella/cli/bitfield.rb +121 -0
- data/lib/tessella/cli/chart.rb +615 -0
- data/lib/tessella/cli/chart_candle.rb +36 -0
- data/lib/tessella/cli/chart_heatmap.rb +21 -0
- data/lib/tessella/cli/chart_help.rb +85 -0
- data/lib/tessella/cli/chart_pie.rb +38 -0
- data/lib/tessella/cli/compose.rb +156 -0
- data/lib/tessella/cli/compose_options.rb +259 -0
- data/lib/tessella/cli/glyphs.rb +97 -0
- data/lib/tessella/cli/image.rb +250 -0
- data/lib/tessella/cli/input.rb +44 -0
- data/lib/tessella/cli/iso_time.rb +28 -0
- data/lib/tessella/cli/lint.rb +102 -0
- data/lib/tessella/cli/lint_options.rb +99 -0
- data/lib/tessella/cli/main.rb +121 -0
- data/lib/tessella/cli/meter.rb +80 -0
- data/lib/tessella/cli/rule.rb +57 -0
- data/lib/tessella/cli/seq.rb +117 -0
- data/lib/tessella/cli/sparkline.rb +72 -0
- data/lib/tessella/cli/table.rb +167 -0
- data/lib/tessella/cli/text.rb +78 -0
- data/lib/tessella/cli/tree.rb +100 -0
- data/lib/tessella/cli/wave.rb +148 -0
- data/lib/tessella/cli.rb +36 -0
- data/lib/tessella/color.rb +106 -0
- data/lib/tessella/compose.rb +243 -0
- data/lib/tessella/compose_fill.rb +176 -0
- data/lib/tessella/compose_grid.rb +36 -0
- data/lib/tessella/compose_shadow.rb +41 -0
- data/lib/tessella/error.rb +13 -0
- data/lib/tessella/frame.rb +170 -0
- data/lib/tessella/glyphs.rb +106 -0
- data/lib/tessella/image.rb +454 -0
- data/lib/tessella/image_octant.rb +46 -0
- data/lib/tessella/image_petscii.rb +116 -0
- data/lib/tessella/kinds/bar.rb +344 -0
- data/lib/tessella/kinds/box.rb +213 -0
- data/lib/tessella/kinds/box_upright.rb +110 -0
- data/lib/tessella/kinds/candlestick.rb +164 -0
- data/lib/tessella/kinds/columns.rb +259 -0
- data/lib/tessella/kinds/date_axis.rb +102 -0
- data/lib/tessella/kinds/error_bars.rb +71 -0
- data/lib/tessella/kinds/heatmap.rb +130 -0
- data/lib/tessella/kinds/line.rb +173 -0
- data/lib/tessella/kinds/multibar.rb +98 -0
- data/lib/tessella/kinds/multibar_columns.rb +130 -0
- data/lib/tessella/kinds/multibar_fine.rb +45 -0
- data/lib/tessella/kinds/multibar_rows.rb +93 -0
- data/lib/tessella/kinds/pie.rb +123 -0
- data/lib/tessella/kinds/pie_rim.rb +78 -0
- data/lib/tessella/kinds/sideways.rb +62 -0
- data/lib/tessella/kinds/stem.rb +134 -0
- data/lib/tessella/kinds/threshold.rb +73 -0
- data/lib/tessella/legend.rb +31 -0
- data/lib/tessella/lint/alphabet.rb +56 -0
- data/lib/tessella/lint/drift.rb +82 -0
- data/lib/tessella/lint/fences.rb +51 -0
- data/lib/tessella/lint/geometry.rb +120 -0
- data/lib/tessella/lint/geometry_checks.rb +224 -0
- data/lib/tessella/lint/lines.rb +115 -0
- data/lib/tessella/lint/mixing.rb +61 -0
- data/lib/tessella/lint/runner.rb +73 -0
- data/lib/tessella/lint/shadows.rb +120 -0
- data/lib/tessella/lint/target_rows.rb +66 -0
- data/lib/tessella/lint.rb +158 -0
- data/lib/tessella/meter.rb +133 -0
- data/lib/tessella/meter_upright.rb +43 -0
- data/lib/tessella/png.rb +456 -0
- data/lib/tessella/raster.rb +92 -0
- data/lib/tessella/rmagick_reader.rb +52 -0
- data/lib/tessella/rule.rb +59 -0
- data/lib/tessella/seq.rb +522 -0
- data/lib/tessella/sparkline.rb +57 -0
- data/lib/tessella/stats.rb +199 -0
- data/lib/tessella/table.rb +547 -0
- data/lib/tessella/table_cells.rb +225 -0
- data/lib/tessella/target.rb +105 -0
- data/lib/tessella/text.rb +76 -0
- data/lib/tessella/text_forms.rb +150 -0
- data/lib/tessella/tree.rb +221 -0
- data/lib/tessella/version.rb +5 -0
- data/lib/tessella/wave.rb +353 -0
- data/lib/tessella/width.rb +122 -0
- data/lib/tessella/width_class.rb +77 -0
- data/lib/tessella/width_invisible.rb +66 -0
- data/lib/tessella/width_invisible_high.rb +60 -0
- data/lib/tessella.rb +94 -0
- metadata +226 -0
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Tessella
|
|
4
|
+
# Binning, percentiles, and the value transforms a chart's scale can
|
|
5
|
+
# apply before drawing (DESIGN.md, Stats and Axis).
|
|
6
|
+
module Stats
|
|
7
|
+
# edges ascending; counts.size == edges.size - 1; every value
|
|
8
|
+
# lands in exactly one bin (DESIGN.md).
|
|
9
|
+
Histogram = Data.define(:edges, :counts, :closed)
|
|
10
|
+
|
|
11
|
+
TRANSFORMS = {
|
|
12
|
+
ln: ->(x) { Math.log(x) },
|
|
13
|
+
log2: ->(x) { Math.log2(x) },
|
|
14
|
+
log10: ->(x) { Math.log10(x) }
|
|
15
|
+
}.freeze
|
|
16
|
+
IDENTITY = ->(x) { x }.freeze
|
|
17
|
+
|
|
18
|
+
# bins: nil picks the count enumerable-statistics' Array#histogram
|
|
19
|
+
# picks for the same data (Sturges' rule); closed: :left means
|
|
20
|
+
# [a, b), :right (a, b].
|
|
21
|
+
def self.histogram(values, bins: nil, closed: :left)
|
|
22
|
+
left = left_closed?(closed)
|
|
23
|
+
return empty_histogram(left) if values.empty?
|
|
24
|
+
|
|
25
|
+
edges = histogram_edges(values, bins, left)
|
|
26
|
+
counts = bin_counts(values, edges, left)
|
|
27
|
+
Histogram.new(edges: edges, counts: counts,
|
|
28
|
+
closed: left ? :left : :right)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# ps in 0..100, linear interpolation between order statistics
|
|
32
|
+
# (the definition enumerable-statistics uses).
|
|
33
|
+
def self.percentiles(values, ps)
|
|
34
|
+
sorted = values.sort
|
|
35
|
+
ps.map { percentile(sorted, it) }
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def self.transform(name)
|
|
39
|
+
return IDENTITY if name.nil? || name == :identity
|
|
40
|
+
|
|
41
|
+
TRANSFORMS.fetch(name) do
|
|
42
|
+
raise ArgumentError, "unknown transform: #{name.inspect}"
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def self.left_closed?(closed)
|
|
47
|
+
return true if closed == :left
|
|
48
|
+
return false if closed == :right
|
|
49
|
+
|
|
50
|
+
raise ArgumentError,
|
|
51
|
+
"closed must be :left or :right, got #{closed.inspect}"
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def self.empty_histogram(left)
|
|
55
|
+
Histogram.new(edges: [0.0], counts: [],
|
|
56
|
+
closed: left ? :left : :right)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def self.percentile(sorted, p)
|
|
60
|
+
raise ArgumentError, "percentile out of bounds" if
|
|
61
|
+
p.negative? || p > 100
|
|
62
|
+
|
|
63
|
+
n = sorted.size
|
|
64
|
+
return sorted.first.to_f if n == 1
|
|
65
|
+
|
|
66
|
+
d = (n - 1) * p / 100.0
|
|
67
|
+
i = d.floor
|
|
68
|
+
interpolate(sorted, i, d - i, n)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def self.interpolate(sorted, i, f, n)
|
|
72
|
+
return sorted[i].to_f if f.zero? || i == n - 1
|
|
73
|
+
|
|
74
|
+
((sorted[i] * (1 - f)) + (sorted[i + 1] * f)).to_f
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def self.histogram_edges(values, bins, left)
|
|
78
|
+
nbins = bins || sturges(values.size)
|
|
79
|
+
raise ArgumentError, "bins must be >= 1, got #{nbins}" if
|
|
80
|
+
nbins < 1
|
|
81
|
+
|
|
82
|
+
lo, hi = values.minmax.map(&:to_f)
|
|
83
|
+
Edges.calculate(lo, hi, nbins, left)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def self.sturges(n) = n.zero? ? 1 : (Math.log2(n).ceil + 1)
|
|
87
|
+
|
|
88
|
+
def self.bin_counts(values, edges, left)
|
|
89
|
+
counts = Array.new(edges.size - 1, 0)
|
|
90
|
+
values.each do |v|
|
|
91
|
+
bi = bin_index(edges, v.to_f, left)
|
|
92
|
+
counts[bi] += 1 if bi.between?(0, counts.size - 1)
|
|
93
|
+
end
|
|
94
|
+
counts
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def self.bin_index(edges, x, left)
|
|
98
|
+
inside = left ? ->(e) { e <= x } : ->(e) { e < x }
|
|
99
|
+
lo = -1
|
|
100
|
+
hi = edges.size
|
|
101
|
+
while hi - lo > 1
|
|
102
|
+
mid = lo + ((hi - lo) / 2)
|
|
103
|
+
inside.call(edges[mid]) ? lo = mid : hi = mid
|
|
104
|
+
end
|
|
105
|
+
left ? lo : hi - 1
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# The edges unicode_plot's Array#histogram computes: a bin width
|
|
109
|
+
# rounded to a nice step (1/2/5 x a power of ten), then widened
|
|
110
|
+
# on both sides until it covers the data (closed-side dependent).
|
|
111
|
+
module Edges
|
|
112
|
+
State = Struct.new(:start, :step, :divisor, :len)
|
|
113
|
+
|
|
114
|
+
def self.calculate(lo, hi, nbins, left)
|
|
115
|
+
state = state_for(lo, hi, nbins)
|
|
116
|
+
extend!(state, lo, hi, left)
|
|
117
|
+
build(state)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def self.state_for(lo, hi, nbins)
|
|
121
|
+
return State.new(hi, unit_step(hi), 1.0, 1) if lo == hi
|
|
122
|
+
|
|
123
|
+
bw = (hi - lo) / nbins
|
|
124
|
+
lbw = Math.log10(bw)
|
|
125
|
+
return wide(lo, hi, bw, lbw) if lbw >= 0
|
|
126
|
+
|
|
127
|
+
narrow(lo, hi, bw, lbw)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# A constant's one bin is [v, v + 1), as unicode_plot's; from
|
|
131
|
+
# about 1e16 up v + 1 == v, so the width grows by tens until
|
|
132
|
+
# the bin holds v.
|
|
133
|
+
def self.unit_step(v)
|
|
134
|
+
step = 1.0
|
|
135
|
+
step *= 10 while v + step == v
|
|
136
|
+
step
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def self.wide(lo, hi, bw, lbw)
|
|
140
|
+
base = 10.0**lbw.floor
|
|
141
|
+
step = base * multiplier(bw / base)
|
|
142
|
+
start = step * (lo / step).floor
|
|
143
|
+
State.new(start, step, 1.0, ((hi - start) / step).ceil)
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def self.narrow(lo, hi, bw, lbw)
|
|
147
|
+
base = 10.0**-lbw.floor
|
|
148
|
+
divisor = base / multiplier(bw * base)
|
|
149
|
+
start = (lo * divisor).floor
|
|
150
|
+
State.new(start, 1.0, divisor,
|
|
151
|
+
((hi * divisor) - start).ceil)
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def self.multiplier(r)
|
|
155
|
+
return 1 if r <= 1.1
|
|
156
|
+
return 2 if r <= 2.2
|
|
157
|
+
return 5 if r <= 5.5
|
|
158
|
+
|
|
159
|
+
10
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def self.extend!(state, lo, hi, left)
|
|
163
|
+
shrink!(state, lo, left)
|
|
164
|
+
grow!(state, hi, left)
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def self.shrink!(state, lo, left)
|
|
168
|
+
if left
|
|
169
|
+
state.start -= state.step while
|
|
170
|
+
lo < state.start / state.divisor
|
|
171
|
+
else
|
|
172
|
+
state.start -= state.step while
|
|
173
|
+
lo <= state.start / state.divisor
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def self.grow!(state, hi, left)
|
|
178
|
+
if left
|
|
179
|
+
state.len += 1 while at(state, state.len) <= hi
|
|
180
|
+
else
|
|
181
|
+
state.len += 1 while at(state, state.len) < hi
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def self.at(state, len)
|
|
186
|
+
(state.start + ((len - 1) * state.step)) / state.divisor
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def self.build(state)
|
|
190
|
+
edges = []
|
|
191
|
+
state.len.times do
|
|
192
|
+
edges << (state.start / state.divisor)
|
|
193
|
+
state.start += state.step
|
|
194
|
+
end
|
|
195
|
+
edges
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
end
|