ccharts 3.0.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.
@@ -0,0 +1,365 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ccharts
4
+ # Settings builders for each chart type. Every field is optional — unset
5
+ # fields take the C library's defaults (which is what the conformance suite
6
+ # checks byte-for-byte), and `plain` forces no ANSI escapes at all.
7
+ #
8
+ # Each builder has a {#to_ffi} that materialises the matching C struct and
9
+ # returns [struct, keepalive], where keepalive holds every backing
10
+ # Fiddle::Pointer (labels, color escapes, ...) alive across the render call.
11
+ module Settings
12
+ # line / candle
13
+ class ChartSettings
14
+ def initialize
15
+ @rise = @fall = @bg = @area = nil
16
+ @single_color = false
17
+ @show_prices = false
18
+ @show_times = false
19
+ @plain = false
20
+ end
21
+
22
+ def rise(v); @rise = v; self; end
23
+ def fall(v); @fall = v; self; end
24
+ def background(v); @bg = v; self; end
25
+ def area(v); @area = v; self; end
26
+ def single_color(v); @single_color = !!v; self; end
27
+ def show_prices(v); @show_prices = !!v; self; end
28
+ def show_times(v); @show_times = !!v; self; end
29
+ def plain(v); @plain = !!v; self; end
30
+
31
+ def to_ffi
32
+ s = FFI::Settings.malloc
33
+ keep = []
34
+ set = lambda do |field, val|
35
+ p = FFI.color_ptr(val, @plain)
36
+ keep << p
37
+ s[field] = p.to_i
38
+ end
39
+ set.call(:rise_color, @rise)
40
+ set.call(:fall_color, @fall)
41
+ set.call(:bg_color, @bg)
42
+ set.call(:area_color, @area)
43
+ s[:single_color] = @single_color ? 1 : 0
44
+ s[:show_prices] = @show_prices ? 1 : 0
45
+ s[:show_times] = @show_times ? 1 : 0
46
+ [s, keep]
47
+ end
48
+ end
49
+
50
+ # pie
51
+ class PieSettings
52
+ def initialize
53
+ @donut = false
54
+ @colors = nil
55
+ @show_legend = true
56
+ @show_pct = false
57
+ @slice_gap = 0.0
58
+ @inner_radius_ratio = -1.0
59
+ @legend_format = 0
60
+ @start_angle = -1.0
61
+ @counter_clockwise = false
62
+ @center_text = nil
63
+ end
64
+
65
+ def donut(v); @donut = !!v; self; end
66
+ def colors(v); @colors = v; self; end
67
+ def show_legend(v); @show_legend = !!v; self; end
68
+ def show_pct(v); @show_pct = !!v; self; end
69
+ def slice_gap(v); @slice_gap = v; self; end
70
+ def inner_radius_ratio(v); @inner_radius_ratio = v; self; end
71
+ def legend_format(v); @legend_format = v; self; end
72
+ def start_angle(v); @start_angle = v; self; end
73
+ def counter_clockwise(v); @counter_clockwise = !!v; self; end
74
+ def center_text(v); @center_text = v; self; end
75
+
76
+ # Packed scalar arguments in ccharts_pie_from_slices order (the settings
77
+ # struct has no pie-specific fields; they are flat function arguments).
78
+ def _scalars
79
+ [@donut ? 1 : 0, @show_legend ? 1 : 0, @show_pct ? 1 : 0,
80
+ @slice_gap, @inner_radius_ratio, @legend_format, @start_angle,
81
+ @counter_clockwise ? 1 : 0]
82
+ end
83
+
84
+ def colors_count
85
+ @colors ? @colors.length : 0
86
+ end
87
+
88
+ def center_text_ptr(keep)
89
+ return Fiddle::NULL if @center_text.nil?
90
+ p = FFI.cstr(@center_text)
91
+ keep << p
92
+ p
93
+ end
94
+
95
+ # Array of color escape pointers (index mod count), or Fiddle::NULL.
96
+ def colors_ptr(keep)
97
+ return Fiddle::NULL if @colors.nil? || @colors.empty?
98
+ ptrs = @colors.map do |c|
99
+ p = FFI.color_ptr(c, false)
100
+ keep << p
101
+ p
102
+ end
103
+ Fiddle::Pointer[ptrs.pack("Q*")]
104
+ end
105
+ end
106
+
107
+ # histogram
108
+ class HistSettings
109
+ def initialize
110
+ @rise = nil
111
+ @bg = nil
112
+ @bin_count = 0
113
+ @min_value = Float::NAN
114
+ @max_value = Float::NAN
115
+ @show_bins = false
116
+ @show_prices = false
117
+ @plain = false
118
+ end
119
+
120
+ def rise(v); @rise = v; self; end
121
+ def background(v); @bg = v; self; end
122
+ def bin_count(v); @bin_count = v; self; end
123
+ def min_value(v); @min_value = v; self; end
124
+ def max_value(v); @max_value = v; self; end
125
+ def show_bins(v); @show_bins = !!v; self; end
126
+ def show_prices(v); @show_prices = !!v; self; end
127
+ def plain(v); @plain = !!v; self; end
128
+
129
+ def to_ffi
130
+ s = FFI::HistSettings.malloc
131
+ keep = []
132
+ set = lambda do |field, val|
133
+ p = FFI.color_ptr(val, @plain)
134
+ keep << p
135
+ s[field] = p.to_i
136
+ end
137
+ set.call(:rise_color, @rise)
138
+ set.call(:bg_color, @bg)
139
+ s[:bin_count] = @bin_count
140
+ s[:min_value] = @min_value
141
+ s[:max_value] = @max_value
142
+ s[:show_bins] = @show_bins ? 1 : 0
143
+ s[:show_prices] = @show_prices ? 1 : 0
144
+ [s, keep]
145
+ end
146
+ end
147
+
148
+ # sparkline
149
+ class SparkSettings
150
+ def initialize
151
+ @rise = nil
152
+ @area = nil
153
+ @min_above = 0
154
+ @min_below = 0
155
+ @plain = false
156
+ end
157
+
158
+ def rise(v); @rise = v; self; end
159
+ def area(v); @area = v; self; end
160
+ def min_above(v); @min_above = v; self; end
161
+ def min_below(v); @min_below = v; self; end
162
+ def plain(v); @plain = !!v; self; end
163
+
164
+ def to_ffi
165
+ s = FFI::SparkSettings.malloc
166
+ keep = []
167
+ set = lambda do |field, val|
168
+ p = FFI.color_ptr(val, @plain)
169
+ keep << p
170
+ s[field] = p.to_i
171
+ end
172
+ set.call(:rise_color, @rise)
173
+ set.call(:area_color, @area)
174
+ s[:min_above] = @min_above
175
+ s[:min_below] = @min_below
176
+ [s, keep]
177
+ end
178
+ end
179
+
180
+ # bar
181
+ class BarSettings
182
+ def initialize
183
+ @rise = nil
184
+ @bg = nil
185
+ @show_labels = false
186
+ @show_prices = false
187
+ @plain = false
188
+ end
189
+
190
+ def rise(v); @rise = v; self; end
191
+ def background(v); @bg = v; self; end
192
+ def show_labels(v); @show_labels = !!v; self; end
193
+ def show_prices(v); @show_prices = !!v; self; end
194
+ def plain(v); @plain = !!v; self; end
195
+
196
+ def to_ffi
197
+ s = FFI::BarSettings.malloc
198
+ keep = []
199
+ set = lambda do |field, val|
200
+ p = FFI.color_ptr(val, @plain)
201
+ keep << p
202
+ s[field] = p.to_i
203
+ end
204
+ set.call(:rise_color, @rise)
205
+ set.call(:bg_color, @bg)
206
+ s[:show_labels] = @show_labels ? 1 : 0
207
+ s[:show_prices] = @show_prices ? 1 : 0
208
+ [s, keep]
209
+ end
210
+ end
211
+
212
+ # stacked bar
213
+ class StackSettings
214
+ def initialize
215
+ @colors = nil
216
+ @bg = nil
217
+ @cat_labels = nil
218
+ @show_labels = false
219
+ @show_prices = false
220
+ @plain = false
221
+ end
222
+
223
+ def colors(v); @colors = v; self; end
224
+ def background(v); @bg = v; self; end
225
+ def category_labels(v); @cat_labels = v; self; end
226
+ def show_labels(v); @show_labels = !!v; self; end
227
+ def show_prices(v); @show_prices = !!v; self; end
228
+ def plain(v); @plain = !!v; self; end
229
+
230
+ # inject the series/category counts known only at render time
231
+ def counts(series_count, cats_count)
232
+ @series_count = series_count
233
+ @cats_count = cats_count
234
+ self
235
+ end
236
+
237
+ def to_ffi
238
+ s = FFI::StackSettings.malloc
239
+ keep = []
240
+
241
+ # NULL-terminated per-series color pointer array.
242
+ color_ptrs = []
243
+ if @plain
244
+ @series_count.times { color_ptrs << FFI::EMPTY.to_i }
245
+ elsif @colors && !@colors.empty?
246
+ @colors.each do |c|
247
+ p = FFI.color_ptr(c, false)
248
+ keep << p
249
+ color_ptrs << p.to_i
250
+ end
251
+ end
252
+ colors_addr = if color_ptrs.empty?
253
+ Fiddle::NULL
254
+ else
255
+ Fiddle::Pointer[(color_ptrs + [0]).pack("Q*")].to_i
256
+ end
257
+
258
+ bgp = FFI.color_ptr(@bg, @plain)
259
+ keep << bgp
260
+
261
+ cat_ptrs = []
262
+ if @cat_labels && !@cat_labels.empty?
263
+ @cat_labels.each do |l|
264
+ p = FFI.cstr(l)
265
+ keep << p
266
+ cat_ptrs << p.to_i
267
+ end
268
+ end
269
+ cat_addr = cat_ptrs.empty? ? Fiddle::NULL : Fiddle::Pointer[cat_ptrs.pack("Q*")].to_i
270
+
271
+ s.colors = colors_addr
272
+ s.bg_color = bgp.to_i
273
+ s.cat_labels = cat_addr
274
+ s.series = @series_count
275
+ s.cats = @cats_count
276
+ s.show_labels = @show_labels ? 1 : 0
277
+ s.show_prices = @show_prices ? 1 : 0
278
+ [s, keep]
279
+ end
280
+ end
281
+
282
+ # heatmap
283
+ class HeatSettings
284
+ def initialize
285
+ @low = nil
286
+ @high = nil
287
+ @mid = nil
288
+ @bg = nil
289
+ @row_labels = nil
290
+ @col_labels = nil
291
+ @show_labels = false
292
+ @plain = false
293
+ end
294
+
295
+ def low_color(v); @low = v; self; end
296
+ def high_color(v); @high = v; self; end
297
+ def mid_color(v); @mid = v; self; end
298
+ def background(v); @bg = v; self; end
299
+ def row_labels(v); @row_labels = v; self; end
300
+ def col_labels(v); @col_labels = v; self; end
301
+ def show_labels(v); @show_labels = !!v; self; end
302
+ def plain(v); @plain = !!v; self; end
303
+
304
+ def label_ptr_array(labels, keep)
305
+ return Fiddle::NULL if labels.nil? || labels.empty?
306
+ ptrs = labels.map do |l|
307
+ p = FFI.cstr(l)
308
+ keep << p
309
+ p.to_i
310
+ end
311
+ Fiddle::Pointer[ptrs.pack("Q*")]
312
+ end
313
+
314
+ def to_ffi
315
+ s = FFI::HeatSettings.malloc
316
+ keep = []
317
+ set = lambda do |field, val|
318
+ p = FFI.color_ptr(val, @plain)
319
+ keep << p
320
+ s[field] = p.to_i
321
+ end
322
+ set.call(:low_color, @low)
323
+ set.call(:high_color, @high)
324
+ set.call(:mid_color, @mid)
325
+ set.call(:bg_color, @bg)
326
+ s[:row_labels] = label_ptr_array(@row_labels, keep).to_i
327
+ s[:col_labels] = label_ptr_array(@col_labels, keep).to_i
328
+ s[:show_labels] = @show_labels ? 1 : 0
329
+ [s, keep]
330
+ end
331
+ end
332
+
333
+ # box plot
334
+ class BoxSettings
335
+ def initialize
336
+ @rise = nil
337
+ @area = nil
338
+ @bg = nil
339
+ @show_prices = false
340
+ @plain = false
341
+ end
342
+
343
+ def rise(v); @rise = v; self; end
344
+ def area(v); @area = v; self; end
345
+ def background(v); @bg = v; self; end
346
+ def show_prices(v); @show_prices = !!v; self; end
347
+ def plain(v); @plain = !!v; self; end
348
+
349
+ def to_ffi
350
+ s = FFI::BoxSettings.malloc
351
+ keep = []
352
+ set = lambda do |field, val|
353
+ p = FFI.color_ptr(val, @plain)
354
+ keep << p
355
+ s[field] = p.to_i
356
+ end
357
+ set.call(:rise_color, @rise)
358
+ set.call(:area_color, @area)
359
+ set.call(:bg_color, @bg)
360
+ s[:show_prices] = @show_prices ? 1 : 0
361
+ [s, keep]
362
+ end
363
+ end
364
+ end
365
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ccharts
4
+ # Version of the Ruby gem, kept in lock-step with the C library's
5
+ # CCHARTS_VERSION (enforced by scripts/check_versions.py).
6
+ VERSION = "3.0.0"
7
+ end
data/lib/ccharts.rb ADDED
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ # ccharts — financial OHLC data as a string.
4
+ #
5
+ # Draws line, candlestick, pie/donut, histogram, sparkline, bar, stacked-bar,
6
+ # heatmap and box-plot charts with Unicode block characters and optional ANSI
7
+ # color. Ships the vendored C ABI (ext/ccharts/vendor) which is compiled from
8
+ # source by mkmf and driven through Fiddle, so the runtime is dependency-free.
9
+ #
10
+ # chart = Ccharts::Chart.from_arrays(open: [...], high: [...], low: [...], close: [...])
11
+ # puts chart.line(60, 8)
12
+ # puts Ccharts::Chart.pie([{ label: "A", value: 40 }, { label: "B", value: 60 }], 24, 10)
13
+ require_relative "ccharts/version"
14
+ require_relative "ccharts/color"
15
+ require_relative "ccharts/ffi"
16
+ require_relative "ccharts/settings"
17
+ require_relative "ccharts/chart"
18
+
19
+ require "fiddle"
20
+
21
+ module Ccharts
22
+ # Raised when the C layer reports a non-OK status, carrying the library's
23
+ # human-readable message.
24
+ class Error < StandardError
25
+ def self.for_status(status)
26
+ msg = FFI.read_cstr(FFI::ERROR_MSG.call(status)) || "unknown ccharts error"
27
+ new("#{msg} (status #{status})")
28
+ end
29
+ end
30
+
31
+ # Raise if `status` is not CCHARTS_OK.
32
+ def self.raise_on(status)
33
+ return if status == 0
34
+ raise Error.for_status(status)
35
+ end
36
+
37
+ # Version of the underlying C library.
38
+ def self.version
39
+ FFI.read_cstr(FFI::VERSION.call)
40
+ end
41
+
42
+ # CC_MAX_DIM / CC_MAX_CELLS limits exposed by the ABI.
43
+ def self.max_dim
44
+ FFI::MAX_DIM.call
45
+ end
46
+
47
+ def self.max_cells
48
+ FFI::MAX_CELLS.call
49
+ end
50
+ end
metadata ADDED
@@ -0,0 +1,73 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ccharts
3
+ version: !ruby/object:Gem::Version
4
+ version: 3.0.0
5
+ platform: ruby
6
+ authors:
7
+ - ccharts contributors
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2026-08-25 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rake
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '13.0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '13.0'
27
+ description: A Ruby binding to the ccharts single-header C library. Turns OHLC / sample
28
+ data into text charts (line, candle, pie/donut, histogram, sparkline, bar, stacked
29
+ bar, heatmap, box plot). The vendored C ABI is compiled from source by mkmf and
30
+ driven through Fiddle, so there are no runtime dependencies beyond the Ruby standard
31
+ library.
32
+ email:
33
+ executables: []
34
+ extensions:
35
+ - ext/ccharts/extconf.rb
36
+ extra_rdoc_files: []
37
+ files:
38
+ - ext/ccharts/extconf.rb
39
+ - ext/ccharts/vendor/ccharts.h
40
+ - ext/ccharts/vendor/ccharts_abi.c
41
+ - ext/ccharts/vendor/ccharts_abi.h
42
+ - lib/ccharts.rb
43
+ - lib/ccharts/chart.rb
44
+ - lib/ccharts/color.rb
45
+ - lib/ccharts/ffi.rb
46
+ - lib/ccharts/settings.rb
47
+ - lib/ccharts/version.rb
48
+ homepage: https://github.com/dethrandir/ccharts
49
+ licenses:
50
+ - MIT
51
+ metadata: {}
52
+ post_install_message:
53
+ rdoc_options: []
54
+ require_paths:
55
+ - lib
56
+ required_ruby_version: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: 2.7.0
61
+ required_rubygems_version: !ruby/object:Gem::Requirement
62
+ requirements:
63
+ - - ">="
64
+ - !ruby/object:Gem::Version
65
+ version: '0'
66
+ requirements: []
67
+ rubygems_version: 3.5.22
68
+ signing_key:
69
+ specification_version: 4
70
+ summary: Financial OHLC data as a string — line, candle, pie, histogram, sparkline,
71
+ bar, stacked-bar, heatmap and box-plot charts drawn with Unicode block characters
72
+ and optional ANSI color.
73
+ test_files: []