Class: Diagrams::TimelineDiagram

Inherits:
Base
  • Object
show all
Defined in:
lib/diagrams/timeline_diagram.rb

Overview

Represents a timeline diagram illustrating a chronology of events.

Constant Summary collapse

DEFAULT_SECTION_TITLE =
'Default Section'

Instance Attribute Summary collapse

Attributes inherited from Base

#checksum, #version

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from Base

#diff, from_hash, from_json, #to_h, #to_json

Constructor Details

#initialize(title: nil, sections: [], version: 1) ⇒ TimelineDiagram

Initializes a new TimelineDiagram.



15
16
17
18
19
20
21
22
# File 'lib/diagrams/timeline_diagram.rb', line 15

def initialize(title: nil, sections: [], version: 1)
  super(version:)
  @title = title&.strip
  @sections = Array(sections)
  # Ensure there's always at least a default section if none provided initially
  ensure_default_section if @sections.empty?
  update_checksum!
end

Instance Attribute Details

#sectionsObject (readonly)

Returns the value of attribute sections.



8
9
10
# File 'lib/diagrams/timeline_diagram.rb', line 8

def sections
  @sections
end

#titleObject (readonly)

Returns the value of attribute title.



8
9
10
# File 'lib/diagrams/timeline_diagram.rb', line 8

def title
  @title
end

Class Method Details

.from_h(data_hash, version:, checksum:) ⇒ Object



113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
# File 'lib/diagrams/timeline_diagram.rb', line 113

def self.from_h(data_hash, version:, checksum:)
  title = data_hash[:title] || data_hash['title']
  sections_data = data_hash[:sections] || data_hash['sections'] || []

  sections = sections_data.map do |section_h|
    section_data = section_h.transform_keys(&:to_sym)
    periods_data = section_data[:periods] || []
    periods = periods_data.map do |period_h|
      period_data = period_h.transform_keys(&:to_sym)
      events_data = period_data[:events] || []
      events = events_data.map do |event_h|
        event_data = event_h.transform_keys(&:to_sym)
        Elements::TimelineEvent.new(event_data)
      end
      Elements::TimelinePeriod.new(period_data.merge(events:))
    end
    Elements::TimelineSection.new(section_data.merge(periods:))
  end

  diagram = new(title:, sections:, version:)

  # Optional: Verify checksum
  if checksum && diagram.checksum != checksum
    warn "Checksum mismatch for loaded TimelineDiagram (version: #{version}). Expected #{checksum}, got #{diagram.checksum}."
  end

  diagram
end

Instance Method Details

#add_period(period_label:, events:) ⇒ Elements::TimelinePeriod

Adds a time period with one or more events to the current (last) section.

Raises:

  • (ArgumentError)

    if period_label or any event description is empty.

  • (StandardError)

    if no sections exist (shouldn’t happen due to default section).



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
# File 'lib/diagrams/timeline_diagram.rb', line 63

def add_period(period_label:, events:)
  clean_label = period_label.strip
  raise ArgumentError, 'Period label cannot be empty' if clean_label.empty?

  event_list = Array(events).map(&:strip).reject(&:empty?)
  raise ArgumentError, 'Events cannot be empty' if event_list.empty?

  timeline_events = event_list.map { |desc| Elements::TimelineEvent.new(description: desc) }
  new_period = Elements::TimelinePeriod.new(label: clean_label, events: timeline_events)

  current_section = @sections.last
  raise StandardError, 'Cannot add period: No section available.' unless current_section

  # Add period to the current section's periods array
  # Dry::Struct arrays are immutable, so we need to create a new section object
  updated_periods = current_section.periods + [new_period]
  # Create a completely new section instance with the updated periods array
  updated_section = Elements::TimelineSection.new(title: current_section.title, periods: updated_periods)

  # Find the index of the current section and update it in place
  # Rebuild the sections array, replacing the modified section
  current_section_title = current_section.title
  # Rebuild the sections array by mapping, replacing the target section
  @sections = @sections.map do |section|
    section.title == current_section_title ? updated_section : section
  end

  update_checksum!
  new_period
end

#add_section(section_title) ⇒ Elements::TimelineSection

Adds a new section to the timeline. Subsequent periods/events will be added to this section.

Raises:

  • (ArgumentError)

    if a section with the same title already exists.



40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# File 'lib/diagrams/timeline_diagram.rb', line 40

def add_section(section_title)
  clean_title = section_title.strip
  raise ArgumentError, "Section title '#{clean_title}' cannot be empty" if clean_title.empty?
  raise ArgumentError, "Section with title '#{clean_title}' already exists" if find_section(clean_title)

  # Remove default section if it's empty and we're adding a real one
  if @sections.size == 1 && @sections.first.title == DEFAULT_SECTION_TITLE && @sections.first.periods.empty?
    @sections.clear
  end

  new_section = Elements::TimelineSection.new(title: clean_title)
  @sections << new_section
  update_checksum!
  new_section
end

#identifiable_elementsObject



104
105
106
107
108
109
110
111
# File 'lib/diagrams/timeline_diagram.rb', line 104

def identifiable_elements
  # Sections and Periods are the main identifiable structures. Events are nested.
  # Use section title and period label as identifiers.
  {
    sections: @sections,
    periods: @sections.flat_map(&:periods) # Flatten periods from all sections
  }
end

#set_title(new_title) ⇒ String

Sets the title of the timeline.



28
29
30
31
32
# File 'lib/diagrams/timeline_diagram.rb', line 28

def set_title(new_title)
  @title = new_title.strip
  update_checksum!
  @title
end

#to_h_contentObject

— Base Class Implementation —



96
97
98
99
100
101
102
# File 'lib/diagrams/timeline_diagram.rb', line 96

def to_h_content
  content = {
    sections: @sections.map(&:to_h)
  }
  content[:title] = @title if @title
  content
end