ZahirJS commited on
Commit
7cd1bcd
·
verified ·
1 Parent(s): 11997fe

Create timeline_generator.py

Browse files
Files changed (1) hide show
  1. timeline_generator.py +149 -0
timeline_generator.py ADDED
@@ -0,0 +1,149 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import graphviz
2
+ import json
3
+ from tempfile import NamedTemporaryFile
4
+ 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.
12
+ It must follow the Expected JSON Format Example below.
13
+
14
+ Expected JSON Format Example:
15
+ {
16
+ "title": "AI Development Timeline",
17
+ "events": [
18
+ {
19
+ "id": "event_1",
20
+ "label": "Machine Learning Foundations",
21
+ "date": "1950-1960",
22
+ "description": "Early neural networks and perceptrons"
23
+ },
24
+ {
25
+ "id": "event_2",
26
+ "label": "Expert Systems Era",
27
+ "date": "1970-1980",
28
+ "description": "Rule-based AI systems"
29
+ },
30
+ {
31
+ "id": "event_3",
32
+ "label": "Neural Network Revival",
33
+ "date": "1980-1990",
34
+ "description": "Backpropagation algorithm"
35
+ }
36
+ ]
37
+ }
38
+
39
+ Returns:
40
+ str: The filepath to the generated PNG image file.
41
+ """
42
+ try:
43
+ if not json_input.strip():
44
+ return "Error: Empty input"
45
+
46
+ data = json.loads(json_input)
47
+
48
+ if 'events' not in data:
49
+ raise ValueError("Missing required field: events")
50
+
51
+ dot = graphviz.Digraph(
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
+
64
+ base_color = '#19191a' # Hardcoded base color
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")
71
+
72
+ # Add title node if provided
73
+ if title:
74
+ dot.node(
75
+ 'title',
76
+ title,
77
+ shape='plaintext',
78
+ fontsize='18',
79
+ fontweight='bold',
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)
144
+ return f"{tmp.name}.{output_format}"
145
+
146
+ except json.JSONDecodeError:
147
+ return "Error: Invalid JSON format"
148
+ except Exception as e:
149
+ return f"Error: {str(e)}"