ZahirJS commited on
Commit
b4ddcf6
·
verified ·
1 Parent(s): b3c0c61

Update timeline_generator.py

Browse files
Files changed (1) hide show
  1. timeline_generator.py +106 -50
timeline_generator.py CHANGED
@@ -5,7 +5,7 @@ import os
5
 
6
  def generate_timeline_diagram(json_input: str, output_format: str) -> str:
7
  """
8
- Generates a timeline diagram from JSON input.
9
 
10
  Args:
11
  json_input (str): A JSON string describing the timeline structure.
@@ -14,6 +14,7 @@ def generate_timeline_diagram(json_input: str, output_format: str) -> str:
14
  Expected JSON Format Example:
15
  {
16
  "title": "AI Development Timeline",
 
17
  "events": [
18
  {
19
  "id": "event_1",
@@ -52,12 +53,12 @@ def generate_timeline_diagram(json_input: str, output_format: str) -> str:
52
  name='Timeline',
53
  format='png',
54
  graph_attr={
55
- 'rankdir': 'LR', # Left-to-Right layout (horizontal timeline)
56
- 'splines': 'ortho', # Straight lines
57
  'bgcolor': 'white', # White background
58
  'pad': '0.5', # Padding around the graph
59
- 'nodesep': '1.0', # Spacing between nodes
60
- 'ranksep': '2.0' # Spacing between ranks
61
  }
62
  )
63
 
@@ -65,6 +66,7 @@ def generate_timeline_diagram(json_input: str, output_format: str) -> str:
65
 
66
  title = data.get('title', '')
67
  events = data.get('events', [])
 
68
 
69
  if not events:
70
  raise ValueError("Timeline must contain at least one event")
@@ -80,64 +82,118 @@ def generate_timeline_diagram(json_input: str, output_format: str) -> str:
80
  fontcolor=base_color
81
  )
82
 
83
- # Add timeline events
84
- previous_event_id = None
85
  total_events = len(events)
 
86
 
 
 
 
 
 
87
  for i, event in enumerate(events):
88
- event_id = event.get('id', f'event_{i}')
89
- event_label = event.get('label', f'Event {i+1}')
90
- event_date = event.get('date', '')
91
- event_description = event.get('description', '')
92
-
93
- # Create full label with date and description
94
- if event_date and event_description:
95
- full_label = f"{event_date}\\n{event_label}\\n{event_description}"
96
- elif event_date:
97
- full_label = f"{event_date}\\n{event_label}"
98
- elif event_description:
99
- full_label = f"{event_label}\\n{event_description}"
100
- else:
101
- full_label = event_label
102
-
103
- # Calculate color opacity based on position in timeline
104
- if total_events == 1:
105
- opacity = 'FF'
106
- else:
107
- opacity_value = int(255 * (1.0 - (i * 0.7 / (total_events - 1))))
108
- opacity = format(opacity_value, '02x')
109
 
110
- node_color = f"{base_color}{opacity}"
111
- font_color = 'white' if i < total_events * 0.7 else 'black'
112
 
113
- # Add the event node
114
- dot.node(
115
- event_id,
116
- full_label,
117
- shape='box',
118
- style='filled,rounded',
119
- fillcolor=node_color,
120
- fontcolor=font_color,
121
- fontsize='12',
122
- width='2.5',
123
- height='1.2'
124
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
125
 
126
- # Connect to previous event if exists
127
- if previous_event_id:
128
  dot.edge(
129
- previous_event_id,
130
- event_id,
131
  color='#666666',
132
  arrowsize='0.8',
133
  penwidth='2'
134
  )
135
 
136
- # Connect title to first event if title exists
137
- if title and i == 0:
138
- dot.edge('title', event_id, style='invis')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
139
 
140
- previous_event_id = event_id
 
 
 
 
 
 
 
141
 
142
  with NamedTemporaryFile(delete=False, suffix=f'.{output_format}') as tmp:
143
  dot.render(tmp.name, format=output_format, cleanup=True)
 
5
 
6
  def generate_timeline_diagram(json_input: str, output_format: str) -> str:
7
  """
8
+ Generates a serpentine timeline diagram from JSON input.
9
 
10
  Args:
11
  json_input (str): A JSON string describing the timeline structure.
 
14
  Expected JSON Format Example:
15
  {
16
  "title": "AI Development Timeline",
17
+ "events_per_row": 4,
18
  "events": [
19
  {
20
  "id": "event_1",
 
53
  name='Timeline',
54
  format='png',
55
  graph_attr={
56
+ 'rankdir': 'TB', # Top-to-Bottom for better control
57
+ 'splines': 'ortho', # Straight lines with 90-degree bends
58
  'bgcolor': 'white', # White background
59
  'pad': '0.5', # Padding around the graph
60
+ 'nodesep': '1.5', # Spacing between nodes
61
+ 'ranksep': '1.5' # Spacing between ranks
62
  }
63
  )
64
 
 
66
 
67
  title = data.get('title', '')
68
  events = data.get('events', [])
69
+ events_per_row = data.get('events_per_row', 4) # Default to 4 events per row
70
 
71
  if not events:
72
  raise ValueError("Timeline must contain at least one event")
 
82
  fontcolor=base_color
83
  )
84
 
85
+ # Calculate positions and create serpentine layout
 
86
  total_events = len(events)
87
+ previous_event_id = None
88
 
89
+ # Create invisible nodes for positioning and rank control
90
+ rows = []
91
+ current_row = []
92
+
93
+ # Group events into rows
94
  for i, event in enumerate(events):
95
+ current_row.append(event)
96
+ if len(current_row) == events_per_row or i == total_events - 1:
97
+ rows.append(current_row)
98
+ current_row = []
99
+
100
+ # Process each row and create serpentine connections
101
+ for row_idx, row in enumerate(rows):
102
+ # Determine if row should be reversed (serpentine pattern)
103
+ is_reversed = row_idx % 2 == 1
104
+ if is_reversed:
105
+ row = row[::-1] # Reverse the row for serpentine effect
 
 
 
 
 
 
 
 
 
 
106
 
107
+ # Create invisible nodes for row positioning
108
+ row_nodes = []
109
 
110
+ for event_idx, event in enumerate(row):
111
+ original_idx = events.index(event)
112
+ event_id = event.get('id', f'event_{original_idx}')
113
+ event_label = event.get('label', f'Event {original_idx+1}')
114
+ event_date = event.get('date', '')
115
+ event_description = event.get('description', '')
116
+
117
+ # Create full label with date and description
118
+ if event_date and event_description:
119
+ full_label = f"{event_date}\\n{event_label}\\n{event_description}"
120
+ elif event_date:
121
+ full_label = f"{event_date}\\n{event_label}"
122
+ elif event_description:
123
+ full_label = f"{event_label}\\n{event_description}"
124
+ else:
125
+ full_label = event_label
126
+
127
+ # Calculate color opacity based on original position in timeline
128
+ if total_events == 1:
129
+ opacity = 'FF'
130
+ else:
131
+ opacity_value = int(255 * (1.0 - (original_idx * 0.7 / (total_events - 1))))
132
+ opacity = format(opacity_value, '02x')
133
+
134
+ node_color = f"{base_color}{opacity}"
135
+ font_color = 'white' if original_idx < total_events * 0.7 else 'black'
136
+
137
+ # Add the event node
138
+ dot.node(
139
+ event_id,
140
+ full_label,
141
+ shape='box',
142
+ style='filled,rounded',
143
+ fillcolor=node_color,
144
+ fontcolor=font_color,
145
+ fontsize='12',
146
+ width='2.5',
147
+ height='1.2'
148
+ )
149
+
150
+ row_nodes.append(event_id)
151
 
152
+ # Create horizontal connections within the row
153
+ for i in range(len(row_nodes) - 1):
154
  dot.edge(
155
+ row_nodes[i],
156
+ row_nodes[i + 1],
157
  color='#666666',
158
  arrowsize='0.8',
159
  penwidth='2'
160
  )
161
 
162
+ # Connect to previous row (serpentine connection)
163
+ if row_idx > 0:
164
+ # Connect last node of previous row to first node of current row
165
+ prev_row_nodes = getattr(generate_timeline_diagram, 'prev_row_nodes', [])
166
+ if prev_row_nodes:
167
+ # Connect the end of previous row to start of current row
168
+ if (row_idx - 1) % 2 == 0: # Previous row was left-to-right
169
+ connection_start = prev_row_nodes[-1] # Last node of previous row
170
+ else: # Previous row was right-to-left
171
+ connection_start = prev_row_nodes[0] # First node of previous row (which was last visually)
172
+
173
+ if row_idx % 2 == 0: # Current row is left-to-right
174
+ connection_end = row_nodes[0] # First node of current row
175
+ else: # Current row is right-to-left
176
+ connection_end = row_nodes[-1] # Last node of current row (which will be first visually)
177
+
178
+ dot.edge(
179
+ connection_start,
180
+ connection_end,
181
+ color='#666666',
182
+ arrowsize='0.8',
183
+ penwidth='2'
184
+ )
185
+
186
+ # Store current row nodes for next iteration
187
+ generate_timeline_diagram.prev_row_nodes = row_nodes
188
 
189
+ # Connect title to first event if title exists and this is the first row
190
+ if title and row_idx == 0:
191
+ first_event = row_nodes[0] if row_idx % 2 == 0 else row_nodes[-1]
192
+ dot.edge('title', first_event, style='invis')
193
+
194
+ # Clean up the stored attribute
195
+ if hasattr(generate_timeline_diagram, 'prev_row_nodes'):
196
+ delattr(generate_timeline_diagram, 'prev_row_nodes')
197
 
198
  with NamedTemporaryFile(delete=False, suffix=f'.{output_format}') as tmp:
199
  dot.render(tmp.name, format=output_format, cleanup=True)