Class: Hackmac::Graph

Inherits:
Object
  • Object
show all
Includes:
Formatters, Term::ANSIColor
Defined in:
lib/hackmac/graph.rb,
lib/hackmac/graph/display.rb

Overview

A class that provides graphical display functionality for terminal-based data visualization

The Graph class enables the creation of dynamic, real-time visualizations of data values within a terminal environment. It manages the rendering of graphical representations such as line charts or graphs, updating them continuously based on provided data sources. The class handles terminal control operations, including cursor positioning, color management, and screen clearing to ensure smooth visual updates. It also supports configuration of display parameters like title, formatting strategies for values, update intervals, and color schemes for different data series.

Examples:

graph = Hackmac::Graph.new(
  title: 'CPU Usage',
  value: ->(i) { rand(100) },
  format_value: :as_percent,
  sleep: 1,
  color: 33
)
graph.start
# Starts the graphical display loop
graph = Hackmac::Graph.new(
  title: 'Memory Usage',
  value: ->(i) { `vm_stat`.match(/Pages free: (\d+)/)[1].to_i },
  format_value: :as_bytes,
  sleep: 2
)
graph.start
# Starts a memory usage graph with custom data source and formatting

Defined Under Namespace

Modules: Formatters Classes: Display

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Formatters

#as_bytes, #as_celsius, #as_default, #as_hertz, #as_percent, #derive_color_from_string

Constructor Details

#initialize(title:, value: -> i { 0 }, format_value: nil, sleep: nil, color: nil) ⇒ Graph

The initialize method sets up a Graph instance by configuring its display parameters and internal state

This method configures the graph visualization with title, value provider, formatting options, update interval, and color settings. It initializes internal data structures for storing historical values and manages synchronization through a mutex for thread-safe operations.

Parameters:

  • title (String)

    the title to display at the bottom of the graph

  • value (Proc) (defaults to: -> i { 0 })

    a proc that takes an index and returns a numeric value for plotting

  • format_value (Proc, Symbol, nil) (defaults to: nil)

    formatting strategy for displaying values

  • sleep (Numeric) (defaults to: nil)

    time in seconds between updates

  • color (Integer, Proc, nil) (defaults to: nil)

    color index or proc to determine color dynamically

Raises:

  • (ArgumentError)

    if the sleep parameter is negative



166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
# File 'lib/hackmac/graph.rb', line 166

def initialize(
  title:,
  value: -> i { 0 },
  format_value: nil,
  sleep: nil,
  color: nil
)
  sleep >= 0 or raise ArgumentError, 'sleep has to be >= 0'
  @title        = title
  @value        = value
  @format_value = format_value
  @sleep        = sleep
  @continue     = false
  @data         = []
  @color        = color
  @mutex        = Mutex.new
end

Instance Attribute Details

#dataArray<Object>, ... (readonly, private)

The data reader method provides access to the data attribute that was set during object initialization.

This method returns the value of the data instance variable, which typically contains structured information that has been processed or collected by the object.

Returns:

  • (Array<Object>, Hash, nil)

    the data value stored in the instance variable, or nil if not set



306
307
308
# File 'lib/hackmac/graph.rb', line 306

def data
  @data
end

Instance Method Details

#columnsInteger (private)

The columns method returns the number of columns in the display

This method provides access to the horizontal dimension of the graphical display by returning the total number of columns available for rendering content

Returns:

  • (Integer)

    the number of columns (characters per line) in the display object



283
284
285
# File 'lib/hackmac/graph.rb', line 283

def columns
  @display.columns
end

#format_value(value) ⇒ String (private)

The format_value method processes a given value using the configured formatting strategy

This method applies the appropriate formatting to a value based on the @format_value instance variable configuration It supports different formatting approaches including custom Proc objects, Symbol-based method calls, and default formatting

Parameters:

  • value (Object)

    the value to be formatted according to the configured strategy

Returns:

  • (String)

    the formatted string representation of the input value



327
328
329
330
331
332
333
334
335
336
# File 'lib/hackmac/graph.rb', line 327

def format_value(value)
  case @format_value
  when Proc
    @format_value.(value)
  when Symbol
    send(@format_value, value)
  else
    send(:as_default, value)
  end
end

#full_resetObject (private)

The full_reset method performs a complete reset of the display and terminal state

This method synchronizes access to shared resources using a mutex, then executes a series of terminal control operations to reset the terminal state, clear the screen, move the cursor to the home position, and make the cursor visible. It also initializes new display objects with the current terminal dimensions and updates the internal display state.



457
458
459
460
461
462
463
464
465
466
# File 'lib/hackmac/graph.rb', line 457

def full_reset
  @mutex.synchronize do
    perform reset, clear_screen, move_home, show_cursor
    winsize = Tins::Terminal.winsize
    @display     = Hackmac::Graph::Display.new(*winsize)
    @old_display = Hackmac::Graph::Display.new(*winsize)
    perform @display
    @full_reset = false
  end
end

#install_handlersObject (private)

The install_handlers method sets up signal handlers for graceful shutdown and terminal resize handling

This method configures two signal handlers: one for the exit hook that performs a full reset, and another for the SIGWINCH signal that handles terminal resize events by setting a flag and displaying a sleeping message



474
475
476
477
478
479
480
# File 'lib/hackmac/graph.rb', line 474

def install_handlers
  at_exit { full_reset }
  trap(:SIGWINCH) do
    @full_reset = true
    perform reset, clear_screen, move_home, 'Zzz…'
  end
end

#linesInteger (private)

The lines method returns the number of lines in the display

This method provides access to the vertical dimension of the graphical display by returning the total number of rows available for rendering content

Returns:

  • (Integer)

    the number of lines (rows) in the display object



294
295
296
# File 'lib/hackmac/graph.rb', line 294

def lines
  @display.lines
end

#normalize_value(value) ⇒ Float, Integer (private)

The normalize_value method converts a value to its appropriate numeric representation

This method takes an input value and normalizes it to either a Float or Integer type depending on its original form. If the value is already a Float, it is returned as-is. For all other types, the method attempts to convert the value to an integer using to_i

Parameters:

  • value (Object)

    the value to be normalized

Returns:

  • (Float, Integer)

    the normalized numeric value as either a Float or Integer



439
440
441
442
443
444
445
446
# File 'lib/hackmac/graph.rb', line 439

def normalize_value(value)
  case value
  when Float
    value
  else
    value.to_i
  end
end

#perform(*a) ⇒ Object (private)



272
273
274
# File 'lib/hackmac/graph.rb', line 272

def perform(*a)
  print(*a)
end

#perform_display_diffvoid (private)

This method returns an undefined value.

The perform_display_diff method calculates and displays the difference between the current and previous display states to update only the changed portions of the terminal output

This method synchronizes access to shared display resources using a mutex, then compares the current display with the previous state to determine what needs updating. It handles dimension mismatches by resetting the old display, computes the visual difference, and outputs only the modified portions to reduce terminal update overhead

When the DEBUG_BYTESIZE environment variable is set, it also outputs debugging information about the size of the diff and the time elapsed since the last debug output



406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
# File 'lib/hackmac/graph.rb', line 406

def perform_display_diff
  @mutex.synchronize do
    unless @old_display && @old_display.dimensions == @display.dimensions
      @old_display = @display.dup.clear
    end
    diff = @display - @old_display
    if ENV['DEBUG_BYTESIZE']
      unless @last
        STDERR.puts %w[ size duration ] * ?\t
      else
        STDERR.puts [ diff.size, (Time.now - @last).to_f ] * ?\t
      end
      @last = Time.now
    end
    perform diff
    @display, @old_display = @old_display.clear, @display
    perform move_to(lines, columns)
  end
end

#pick_colorTerm::ANSIColor::Attribute (private)

The pick_color method determines and returns an ANSI color attribute based on the configured color setting

This method evaluates the @color instance variable to decide how to select a color attribute. If @color is a Proc, it invokes the proc with the @title to determine the color. If @color is nil, it derives a color from the title string. Otherwise, it uses the @color value directly as an index into the ANSI color attributes.

Returns:

  • (Term::ANSIColor::Attribute)

    the selected color attribute object



348
349
350
351
352
353
354
355
356
357
358
359
# File 'lib/hackmac/graph.rb', line 348

def pick_color
  Term::ANSIColor::Attribute[
    case @color
    when Proc
      @color.(@title)
    when nil
      derive_color_from_string(@title)
    else
      @color
    end
  ]
end

#sleep_durationString (private)

The sleep_duration method returns a string representation of the configured sleep interval with the ‘s’ suffix appended to indicate seconds.

Returns:

  • (String)

    a formatted string containing the sleep duration in seconds



312
313
314
# File 'lib/hackmac/graph.rb', line 312

def sleep_duration
  "#{@sleep}s"
end

#sleep_nowObject (private)

The sleep_now method calculates and executes a sleep duration based on the configured sleep time and elapsed time since start

This method determines how long to sleep by calculating the difference between the configured sleep interval and the time elapsed since the last operation started. If no start time is recorded, it uses the full configured sleep duration. The method ensures that negative sleep durations are not used by taking the maximum of the calculated duration and zero.



381
382
383
384
385
386
387
388
# File 'lib/hackmac/graph.rb', line 381

def sleep_now
  duration = if @start
               [ @sleep - (Time.now - @start).to_f, 0 ].max
             else
               @sleep
             end
  sleep duration
end

#startObject

The start method initiates the graphical display process by setting up signal handlers, performing an initial terminal reset, and entering the main update loop

This method serves as the entry point for starting the graph visualization functionality. It configures the necessary signal handlers for graceful shutdown and terminal resizing, performs an initial full reset of the display state, and then begins the continuous loop that updates and renders graphical data.



193
194
195
196
197
# File 'lib/hackmac/graph.rb', line 193

def start
  install_handlers
  full_reset
  start_loop
end

#start_loopObject (private)

The start_loop method executes a continuous loop to update and display graphical data

This method manages the main execution loop for rendering graphical representations of data values over time. It initializes display state, processes incoming data, calculates visual representations, and handles terminal updates while respecting configured timing intervals.

It continuously updates the display and handles data processing in a loop until explicitly stopped.



223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
# File 'lib/hackmac/graph.rb', line 223

def start_loop
  full_reset
  color       = pick_color
  color_light = color.to_rgb_triple.to_hsl_triple.lighten(15) rescue color
  @counter    = -1
  @continue = true
  while @continue
    @start = Time.now
    @full_reset and full_reset
    perform hide_cursor

    @data << @value.(@counter += 1)
    @data = data.last(columns)

    y_width = (data.max - data.min).to_f
    if y_width == 0
      @display.reset.bottom.styled(:bold).
        write_centered("#@title / #{sleep_duration}").
        reset.centered.styled(:italic).write_centered("no data")
      perform_display_diff
      sleep_now
      next
    end

    @display.reset
    data.each_with_index do |value, x|
      x = x + columns - data.size + 1
      y = lines - (((value - data.min) * lines / y_width)).round + 1
      y.upto(lines) do |iy|
        @display.at(iy, x).on_color(
          y == iy ? color : color_light
        ).write(' ')
      end
    end

    @display.reset.bottom.styled(:bold).
      write_centered("#@title #{format_value(data.last)} / #{sleep_duration}")
    @display.reset.styled(:bold).
      left.top.write(format_value(data.max)).
      left.bottom.write(format_value(data.min))

    perform_display_diff
    sleep_now
  end
rescue Interrupt
ensure
  stop
end

#stopObject

The stop method terminates the graphical display process by performing a full reset and setting the continue flag to false

This method serves as the shutdown mechanism for the graph visualization functionality. It ensures that all display resources are properly cleaned up and the terminal state is restored to its original condition before stopping the continuous update loop.



206
207
208
209
# File 'lib/hackmac/graph.rb', line 206

def stop
  full_reset
  @continue = false
end