├── writingPLan2025-2026.png ├── LICENSE ├── README.md └── 2025writing.py /writingPLan2025-2026.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MooersLab/gantt-chart-py/main/writingPLan2025-2026.png -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Blaine Mooers and the University of Oklahoma Board of Regents 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Version](https://img.shields.io/static/v1?label=gantt-chart-py&message=0.1&color=brightcolor) 2 | [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT) 3 | 4 | 5 | # Timelines as Gantt chart via matplotlib in Python 6 | 7 | ## Problem addressed 8 | 9 | Gantt charts help keep you realistic about your planning. 10 | They are valuable for planning large, complex projects and managing multiple projects. 11 | However, finding open-source software to make timelines is a pain. 12 | The placeholder plan below is not realistic, but you get the idea. 13 | 14 | ![Writing Plan](writingPLan2025-2026.png) 15 | 16 | 17 | ## Instructions 18 | 1. Edit the path to your Python binary. 19 | 2. Install missing modules with pip. 20 | 3. Relabel the projects in the named tuple to customize. 21 | 4. Edit the start and end dates of each phase of a project in the named tuple to suit. 22 | 5. Add or remove categories of phases if desired. 23 | 7. Change the colors to suit. 24 | 8. Edit the title to suit. 25 | 9. Run `chmod +x 2025writing.py`. 26 | 10. Run the script: `./2025writing.py`. 27 | 11. The resulting image will be displayed and written to a PNG file. 28 | 29 | You can repurpose this chart for a single project. 30 | Change the labels to distinct activity names so the time spans appear at different positions along the y-axis. 31 | 32 | ## Update history 33 | 34 | |Version | Changes | Date | 35 | |:-----------|:------------------------------------------------------------------------------------------------------------------------------------------|:--------------------| 36 | | Version 0.1 | Added badges, funding, and update table. Initial commit. | 2025 April 4 | 37 | 38 | ## Sources of funding 39 | 40 | - NIH: R01 CA242845 41 | - NIH: R01 AI088011 42 | - NIH: P30 CA225520 (PI: R. Mannel) 43 | - NIH: P20 GM103640 and P30 GM145423 (PI: A. West) 44 | -------------------------------------------------------------------------------- /2025writing.py: -------------------------------------------------------------------------------- 1 | #!/opt/local/bin/python3.12 2 | """ 3 | This script makes a great plot for planning manuscript assembly and submission. 4 | The activities per manuscript are on the same line. 5 | The activities are grouped by project name. 6 | A named tuple had to be used to properly align the project name on the y-axis. 7 | 8 | Blaine Mooers and the OUHSC Board of Regents 9 | 10 | April 4, 2025 11 | """ 12 | import matplotlib.pyplot as plt 13 | import matplotlib.dates as mdates 14 | import pandas as pd 15 | import numpy as np 16 | from datetime import datetime, timedelta 17 | from collections import namedtuple 18 | 19 | # Create a named tuple for task data to ensure immutability 20 | Task = namedtuple('Task', ['name', 'start', 'end', 'phase']) 21 | 22 | # Define tasks using the immutable namedtuple: project name, start date, end date, and task. 23 | project_tasks = ( 24 | Task("Paper A", "2025-04-05", "2025-04-30", "Research and Lit Rev"), 25 | Task("Paper A", "2025-05-05", "2025-05-15", "Writing Results and Slideshow"), 26 | Task("Paper A", "2025-05-16", "2025-05-22", "Discussion"), 27 | Task("Paper A", "2025-05-16", "2025-05-25", "Polishing and Submission"), 28 | Task("Paper A", "2025-07-15", "2025-08-25", "Revision, Resubmit, Galley Proofs"), 29 | 30 | Task("Paper B", "2025-04-05", "2025-08-30", "Research and Lit Rev"), 31 | Task("Paper B", "2025-08-30", "2025-10-30", "Writing Results and Slideshow"), 32 | Task("Paper B", "2025-10-31", "2025-12-31", "Discussion"), 33 | Task("Paper B", "2026-01-02", "2026-05-03", "Polishing and Submission"), 34 | Task("Paper B", "2026-07-15", "2026-08-25", "Revision, Resubmit, Galley Proofs"), 35 | 36 | Task("Paper C", "2025-04-05", "2025-04-13", "Research and Lit Rev"), 37 | Task("Paper C", "2025-04-09", "2025-04-13", "Writing Results and Slideshow"), 38 | Task("Paper C", "2025-04-13", "2025-04-21", "Discussion"), 39 | Task("Paper C", "2025-04-21", "2025-04-25", "Polishing and Submission"), 40 | Task("Paper C", "2025-06-25", "2025-08-25", "Revision, Resubmit, Galley Proofs"), 41 | 42 | Task("Paper D", "2025-04-05", "2025-04-13", "Research and Lit Rev"), 43 | Task("Paper D", "2025-04-09", "2025-04-13", "Writing Results and Slideshow"), 44 | Task("Paper D", "2025-04-13", "2025-04-21", "Discussion"), 45 | Task("Paper D", "2025-04-21", "2025-04-30", "Polishing and Submission"), 46 | Task("Paper D", "2025-06-05", "2025-06-30", "Revision, Resubmit, Galley Proofs"), 47 | 48 | Task("Paper E", "2025-04-06", "2025-05-30", "Research and Lit Rev"), 49 | Task("Paper E", "2025-05-01", "2025-06-30", "Writing Results and Slideshow"), 50 | Task("Paper E", "2025-06-30", "2025-07-31", "Discussion"), 51 | Task("Paper E", "2025-07-31", "2025-08-30", "Polishing and Submission"), 52 | Task("Paper E", "2025-10-15", "2025-10-30", "Revision, Resubmit, Galley Proofs"), 53 | 54 | Task("Paper F", "2025-08-01", "2025-08-30", "Research and Lit Rev"), 55 | Task("Paper F", "2025-09-01", "2025-09-30", "Writing Results and Slideshow"), 56 | Task("Paper F", "2025-09-30", "2025-10-31", "Discussion"), 57 | Task("Paper F", "2025-10-31", "2025-11-30", "Polishing and Submission"), 58 | Task("Paper F", "2026-02-01", "2026-02-28", "Revision, Resubmit, Galley Proofs"), 59 | 60 | Task("Paper G", "2025-05-01", "2025-08-30", "Research and Lit Rev"), 61 | Task("Paper G", "2025-08-31", "2025-10-30", "Writing Results and Slideshow"), 62 | Task("Paper G", "2025-10-30", "2025-11-15", "Discussion"), 63 | Task("Paper G", "2025-11-15", "2025-11-30", "Polishing and Submission"), 64 | Task("Paper G", "2026-02-01", "2026-02-28", "Revision, Resubmit, Galley Proofs"), 65 | 66 | ) 67 | 68 | # Define custom phase colors with updated categories 69 | phase_colors = { 70 | "Research and Lit Rev": "#39FF14", # Bright Green 71 | "Writing Results and Slideshow": "#4287f5", # Blue 72 | "Discussion": "#f542bb", # Pink 73 | "Polishing and Submission": "#e84118", # Red 74 | "Revision, Resubmit, Galley Proofs": "#ffd700" # Gold 75 | } 76 | 77 | # Group tasks by name to consolidate y-axis labels 78 | task_names_unique = sorted(set(task.name for task in project_tasks)) 79 | name_to_pos = {name: len(task_names_unique) - i - 1 for i, name in enumerate(task_names_unique)} 80 | 81 | # Create figure and axis 82 | fig, ax = plt.subplots(figsize=(16, 7)) 83 | 84 | # Determine overall date range for x-axis 85 | all_starts = [pd.Timestamp(task.start) for task in project_tasks] 86 | all_ends = [pd.Timestamp(task.end) for task in project_tasks] 87 | min_date = min(all_starts) 88 | max_date = max(all_ends) 89 | 90 | # Plot the tasks 91 | for task in project_tasks: 92 | # Convert dates to timestamps 93 | start_date = pd.Timestamp(task.start) 94 | end_date = pd.Timestamp(task.end) 95 | 96 | # Get y position based on task name 97 | y_pos = name_to_pos[task.name] 98 | 99 | # Calculate duration 100 | duration = (end_date - start_date).days + 1 101 | 102 | # Plot bar 103 | ax.barh(y_pos, 104 | duration, 105 | left=mdates.date2num(start_date), 106 | height=0.5, 107 | align='center', 108 | color=phase_colors[task.phase], 109 | alpha=0.8, 110 | edgecolor='navy') 111 | 112 | # Add phase label inside the bar for longer bars 113 | if duration > 60: 114 | bar_middle = mdates.date2num(start_date) + duration / 2 115 | ax.text(bar_middle, y_pos, task.phase, 116 | ha='center', va='center', color='white', 117 | fontweight='bold', fontsize=9) 118 | 119 | # Set up y-axis 120 | ax.set_yticks(list(name_to_pos.values())) 121 | ax.set_yticklabels(list(name_to_pos.keys())) 122 | ax.set_ylim(-0.5, len(task_names_unique) - 0.5) 123 | 124 | # Generate monthly date range for vertical grid lines 125 | start_month = pd.Timestamp(min_date.year, min_date.month, 1) 126 | end_month = pd.Timestamp(max_date.year, max_date.month, 1) + pd.DateOffset(months=1) 127 | months = pd.date_range(start=start_month, end=end_month, freq='MS') 128 | 129 | # Add vertical lines for each month (first of the month) 130 | for month in months: 131 | ax.axvline(x=mdates.date2num(month), color='gray', linestyle='-', 132 | alpha=0.2, linewidth=1) 133 | 134 | # Set up x-axis with monthly ticks showing both month and year 135 | ax.xaxis.set_major_locator(mdates.MonthLocator()) # First of each month 136 | ax.xaxis.set_major_formatter(mdates.DateFormatter('%b\n%Y')) # Month and year on separate lines 137 | ax.tick_params(axis='x', which='major', labelsize=9, pad=2) 138 | 139 | # Add buffer space to the x-axis 140 | buffer_days = max(10, (max_date - min_date).days * 0.02) # 2% buffer, minimum 10 days 141 | ax.set_xlim( 142 | mdates.date2num(min_date - pd.Timedelta(days=buffer_days)), 143 | mdates.date2num(max_date + pd.Timedelta(days=buffer_days)) 144 | ) 145 | 146 | # Add today vertical line 147 | today = pd.Timestamp('2025-04-03') 148 | plt.axvline(x=mdates.date2num(today), color='red', linestyle='--', 149 | alpha=0.7, label='Start Date') 150 | 151 | # Add legend for phases 152 | handles = [plt.Rectangle((0,0),1,1, color=color, alpha=0.8) for color in phase_colors.values()] 153 | labels = list(phase_colors.keys()) 154 | plt.legend(handles, labels, loc='upper right', title='Manuscript Phases') 155 | 156 | # Add title and labels 157 | plt.title('Journal Article Writing Plan 2025-2026', fontsize=16, pad=20) 158 | plt.xlabel('Month/Year', fontsize=12, labelpad=10) 159 | 160 | # Style and grid 161 | ax.spines['top'].set_visible(False) 162 | ax.spines['right'].set_visible(False) 163 | 164 | # Custom year separator lines (darker line between years) 165 | year_starts = [pd.Timestamp(year=y, month=1, day=1) for y in range(min_date.year, max_date.year + 2)] 166 | for year_date in year_starts: 167 | if year_date >= min_date and year_date <= max_date: 168 | ax.axvline(x=mdates.date2num(year_date), color='black', linestyle='-', 169 | alpha=0.5, linewidth=1.5) 170 | 171 | # Adjust layout to prevent label cutoff 172 | plt.tight_layout() 173 | 174 | 175 | # Uncomment to save the figure 176 | plt.savefig('writingPLan2025-2026.png', dpi=300, bbox_inches='tight') 177 | 178 | # Display the plot 179 | plt.show() 180 | --------------------------------------------------------------------------------