Module: ActiveRecord::Bitemporal::Visualizer
- Defined in:
- lib/activerecord-bitemporal/visualizer.rb
Defined Under Namespace
Classes: Figure
Class Method Summary collapse
-
.compute_lengths_from_beginning(times, outlier: nil) ⇒ Object
Example:.
-
.compute_positions(times, length:, left_margin: 0, outlier: nil) ⇒ Object
Compute a dictionary of where each time should be plotted.
- .visualize(record, height: 10, width: 40, highlight: true) ⇒ Object
-
.visualize_records(*relations, height: 10, width: 40) ⇒ Object
e.g.
Class Method Details
.compute_lengths_from_beginning(times, outlier: nil) ⇒ Object
Example:
t1 t2 t3 t4
|-----------|-----------|-----------|
<-----------> l1
<-----------------------> l2
<-----------------------------------> l3
f([t1, t2, t3, t4]) -> { t1 => 0, t2 => l1, t3 => l2, t4 => l3 }
158 159 160 161 162 163 164 165 166 167 168 169 |
# File 'lib/activerecord-bitemporal/visualizer.rb', line 158 def compute_lengths_from_beginning(times, outlier: nil) times.each_with_object({}) do |time, ret| ret[time] = if time == outlier && times.size > 2 # If it contains an extremely large value such as 9999-12-31, # that point will have a large effect on the visualization, # so adjust the length so that it is half of the whole. ret.values.last * 2 else time - times.min end end end |
.compute_positions(times, length:, left_margin: 0, outlier: nil) ⇒ Object
Compute a dictionary of where each time should be plotted. The position is normalized to the actual length of time.
Example:
t1 t2 t3 t4
|------|------|-----------------|
f(t1, t2, t3, t4) -> { t1 => 0, t2 => 2, t3 => 4, t4 => 10 }
121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 |
# File 'lib/activerecord-bitemporal/visualizer.rb', line 121 def compute_positions(times, length:, left_margin: 0, outlier: nil) lengths_from_beginning = compute_lengths_from_beginning(times, outlier: outlier) # times must be sorted in ascending order. This is caller's responsibility. # In that case, the last of lengths_from_beginning is equal to the total length. total = lengths_from_beginning.values.last times.each_with_object({}) do |time, ret| prev = ret.values.last pos = (lengths_from_beginning[time] / total * length).to_i + left_margin if prev # If the difference of times is too short, a position that have already been plotted may be computed. # But we still want to plot the time, so allocate the required number to plot the smallest area. if pos <= prev # | -> |*| # ^^ 2 columns pos = prev + 2 elsif pos == prev + 1 # || -> |*| # ^ 1 column pos += 1 end end ret[time] = pos end end |
.visualize(record, height: 10, width: 40, highlight: true) ⇒ Object
24 25 26 27 28 29 30 31 32 |
# File 'lib/activerecord-bitemporal/visualizer.rb', line 24 def visualize(record, height: 10, width: 40, highlight: true) histories = record.class.ignore_bitemporal_datetime.bitemporal_for(record).order(:transaction_from, record.valid_from_key) if highlight visualize_records(histories, [record], height: height, width: width) else visualize_records(histories, height: height, width: width) end end |
.visualize_records(*relations, height: 10, width: 40) ⇒ Object
e.g. visualize_records(ActiveRecord::Relation, ActiveRecord::Relation)
35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 |
# File 'lib/activerecord-bitemporal/visualizer.rb', line 35 def visualize_records(*relations, height: 10, width: 40) raise 'More than 3 relations are not supported' if relations.size >= 3 records = relations.flatten valid_times = (records.map { _1[_1.valid_from_key] } + records.map { _1[_1.valid_to_key] }).sort.uniq transaction_times = (records.map(&:transaction_from) + records.map(&:transaction_to)).sort.uniq time_length = Time.zone.now.strftime('%F %T.%3N').length columns = compute_positions(valid_times, length: width, left_margin: time_length + 1, outlier: ActiveRecord::Bitemporal::DEFAULT_VALID_TO) lines = compute_positions(transaction_times, length: height, outlier: ActiveRecord::Bitemporal::DEFAULT_TRANSACTION_TO) headers = Figure.new valid_times.each_with_object([]).with_index do |(valid_time, prev_valid_times), line| prev_valid_times.each do |valid_time| headers.print('|', line: line, column: columns[valid_time]) end valid_time_str = valid_time.kind_of?(Date) ? valid_time.strftime('%F') : valid_time.strftime('%F %T.%3N') headers.print("| #{valid_time_str}", line: line, column: columns[valid_time]) prev_valid_times << valid_time end body = Figure.new relations.each.with_index do |relation, idx| filler = idx == 0 ? ' ' : '*' relation.each do |record| line = lines[record.transaction_from] valid_from = record[record.valid_from_key] valid_to = record[record.valid_to_key] column = columns[valid_from] width = columns[valid_to] - columns[valid_from] - 1 height = lines[record.transaction_to] - lines[record.transaction_from] - 1 body.print("#{record.transaction_from.strftime('%F %T.%3N')} ", line: line) if width > 0 if height > 0 body.print('+' + '-' * width + '+', line: line, column: column) else body.print('|' + '#' * width + '|', line: line, column: column) end else body.print('#', line: line, column: column) end 1.upto(height) do |i| if width > 0 body.print('|' + filler * width + '|', line: line + i, column: column) else body.print('#', line: line + i, column: column) end end body.print("#{record.transaction_to.strftime('%F %T.%3N')} ", line: line + height + 1) if width > 0 body.print('+' + '-' * width + '+', line: line + height + 1, column: column) else body.print('#', line: line + height + 1, column: column) end end end valid_label = valid_times[0].kind_of?(Date) ? 'valid_date' : 'valid_datetime' transaction_label = 'transaction_datetime' right_margin = time_length + 1 - transaction_label.size label = if right_margin >= 0 "#{transaction_label + ' ' * right_margin}| #{valid_label}" else "#{transaction_label[0...right_margin]}| #{valid_label}" end "#{label}\n#{headers.to_s}\n#{body.to_s}" end |