jsonantt 2026.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,166 @@
1
+ Metadata-Version: 2.4
2
+ Name: jsonantt
3
+ Version: 2026.1
4
+ Summary: Generate Gantt charts from JSON descriptions using matplotlib
5
+ Keywords: gantt,chart,matplotlib,project-management,visualization
6
+ Classifier: Development Status :: 3 - Alpha
7
+ Classifier: Intended Audience :: Developers
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Programming Language :: Python :: 3.8
10
+ Classifier: Programming Language :: Python :: 3.9
11
+ Classifier: Programming Language :: Python :: 3.10
12
+ Classifier: Programming Language :: Python :: 3.11
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Topic :: Scientific/Engineering :: Visualization
15
+ Requires-Python: >=3.8
16
+ Description-Content-Type: text/markdown
17
+ Requires-Dist: matplotlib>=3.5
18
+
19
+ # jsonantt
20
+
21
+ **jsonantt** generates beautiful Gantt chart images from a simple JSON description.
22
+ Charts are rendered with [matplotlib](https://matplotlib.org/) so they can be saved as `.png`, `.pdf`, `.svg`, and more.
23
+
24
+ ---
25
+
26
+ ## Features
27
+
28
+ - **Infinitely nestable tasks** — define sub-tasks, sub-sub-tasks, etc.
29
+ - **Auto date computation** — parent task start/end are derived automatically from children when not specified.
30
+ - **Milestone markers** — easy `"milestone": true` flag renders a distinctive diamond.
31
+ - **Fully colourable** — set colours per-task; children inherit their parent's colour.
32
+ - **Clean, indented y-axis labels** — task names are left-aligned with proper indentation per depth level.
33
+ - **PNG / PDF / SVG output** — whatever matplotlib supports.
34
+
35
+ ---
36
+
37
+ ## Installation
38
+
39
+ ```bash
40
+ pip install jsonantt
41
+ ```
42
+
43
+ Or, directly from source:
44
+
45
+ ```bash
46
+ git clone https://github.com/briday1/jsonantt.git
47
+ cd jsonantt
48
+ pip install -e .
49
+ ```
50
+
51
+ ---
52
+
53
+ ## Quick start
54
+
55
+ ### 1. Create a JSON description
56
+
57
+ ```json
58
+ {
59
+ "title": "My Project",
60
+ "dateformat": "%Y-%m-%d",
61
+ "tasks": [
62
+ {
63
+ "name": "Phase 1 – Planning",
64
+ "children": [
65
+ { "name": "Requirements", "start": "2024-01-08", "end": "2024-01-19" },
66
+ { "name": "Architecture", "start": "2024-01-15", "end": "2024-01-31" }
67
+ ]
68
+ },
69
+ { "name": "Planning done", "milestone": true, "date": "2024-01-31" },
70
+ {
71
+ "name": "Phase 2 – Build",
72
+ "color": "#70AD47",
73
+ "children": [
74
+ { "name": "Backend", "start": "2024-02-01", "end": "2024-03-01" },
75
+ { "name": "Frontend", "start": "2024-02-12", "end": "2024-03-08" }
76
+ ]
77
+ },
78
+ { "name": "Launch", "milestone": true, "date": "2024-04-01", "color": "#FF5757" }
79
+ ]
80
+ }
81
+ ```
82
+
83
+ ### 2. Generate the chart
84
+
85
+ **CLI:**
86
+
87
+ ```bash
88
+ jsonantt project.json project.png
89
+ jsonantt project.json project.pdf # vector PDF
90
+ jsonantt project.json project.svg # scalable SVG
91
+ jsonantt --dpi 300 project.json project.png # high-resolution PNG
92
+ ```
93
+
94
+ **Python API:**
95
+
96
+ ```python
97
+ from jsonantt import load_chart, render_chart
98
+
99
+ config = load_chart("project.json")
100
+ render_chart(config, "project.png", dpi=150)
101
+ ```
102
+
103
+ ---
104
+
105
+ ## JSON reference
106
+
107
+ | Field | Type | Description |
108
+ |-------|------|-------------|
109
+ | `title` | string | Optional chart title shown at the top |
110
+ | `dateformat` | string | `strptime` format string (default: `"%Y-%m-%d"`) |
111
+ | `start` | date string | Optional chart x-axis start date (overrides task dates) |
112
+ | `end` | date string | Optional chart x-axis end date |
113
+ | `style` | object | Visual style overrides (see below) |
114
+ | `tasks` | array | Top-level list of task objects |
115
+
116
+ ### Task object
117
+
118
+ | Field | Type | Description |
119
+ |-------|------|-------------|
120
+ | `name` | string | **Required.** Task label |
121
+ | `start` | date string | Bar start date |
122
+ | `end` | date string | Bar end date |
123
+ | `color` | CSS hex string | Bar/milestone colour (e.g. `"#4472C4"`) |
124
+ | `milestone` | boolean | Render as a diamond milestone instead of a bar |
125
+ | `date` | date string | Milestone date (used when `milestone: true`) |
126
+ | `children` | array | Nested sub-tasks (infinitely nestable) |
127
+
128
+ > **Auto date computation:** When a task has `children` but no explicit `start`/`end`, the dates are computed automatically as the earliest child start and latest child end, recursively.
129
+
130
+ ### Style object
131
+
132
+ | Field | Default | Description |
133
+ |-------|---------|-------------|
134
+ | `width` | `14` | Figure width in inches |
135
+ | `row_height` | `0.45` | Height of each task row in inches |
136
+ | `bar_height` | `0.5` | Bar height as a fraction of `row_height` |
137
+ | `font_size` | `9` | Base font size in points |
138
+ | `indent_size` | `3` | Spaces added per depth level in labels |
139
+ | `label_fraction` | `0.28` | Fraction of figure width used for labels |
140
+ | `colors` | palette | Array of default hex colours cycled per top-level task |
141
+ | `background` | `"#FFFFFF"` | Figure background colour |
142
+ | `grid_color` | `"#E0E0E0"` | Vertical gridline colour |
143
+ | `row_band_color` | `"#F5F5F5"` | Alternating row band colour |
144
+ | `milestone_color` | `"#E65100"` | Default milestone colour |
145
+
146
+ ---
147
+
148
+ ## Examples
149
+
150
+ See the [`examples/`](examples/) folder for ready-to-run JSON files:
151
+
152
+ - [`examples/simple.json`](examples/simple.json) — a three-phase project with milestones
153
+ - [`examples/complex.json`](examples/complex.json) — a multi-quarter roadmap with deep nesting and custom colours
154
+
155
+ Generate them locally:
156
+
157
+ ```bash
158
+ jsonantt examples/simple.json simple.png
159
+ jsonantt examples/complex.json complex.png
160
+ ```
161
+
162
+ ---
163
+
164
+ ## License
165
+
166
+ MIT
@@ -0,0 +1,148 @@
1
+ # jsonantt
2
+
3
+ **jsonantt** generates beautiful Gantt chart images from a simple JSON description.
4
+ Charts are rendered with [matplotlib](https://matplotlib.org/) so they can be saved as `.png`, `.pdf`, `.svg`, and more.
5
+
6
+ ---
7
+
8
+ ## Features
9
+
10
+ - **Infinitely nestable tasks** — define sub-tasks, sub-sub-tasks, etc.
11
+ - **Auto date computation** — parent task start/end are derived automatically from children when not specified.
12
+ - **Milestone markers** — easy `"milestone": true` flag renders a distinctive diamond.
13
+ - **Fully colourable** — set colours per-task; children inherit their parent's colour.
14
+ - **Clean, indented y-axis labels** — task names are left-aligned with proper indentation per depth level.
15
+ - **PNG / PDF / SVG output** — whatever matplotlib supports.
16
+
17
+ ---
18
+
19
+ ## Installation
20
+
21
+ ```bash
22
+ pip install jsonantt
23
+ ```
24
+
25
+ Or, directly from source:
26
+
27
+ ```bash
28
+ git clone https://github.com/briday1/jsonantt.git
29
+ cd jsonantt
30
+ pip install -e .
31
+ ```
32
+
33
+ ---
34
+
35
+ ## Quick start
36
+
37
+ ### 1. Create a JSON description
38
+
39
+ ```json
40
+ {
41
+ "title": "My Project",
42
+ "dateformat": "%Y-%m-%d",
43
+ "tasks": [
44
+ {
45
+ "name": "Phase 1 – Planning",
46
+ "children": [
47
+ { "name": "Requirements", "start": "2024-01-08", "end": "2024-01-19" },
48
+ { "name": "Architecture", "start": "2024-01-15", "end": "2024-01-31" }
49
+ ]
50
+ },
51
+ { "name": "Planning done", "milestone": true, "date": "2024-01-31" },
52
+ {
53
+ "name": "Phase 2 – Build",
54
+ "color": "#70AD47",
55
+ "children": [
56
+ { "name": "Backend", "start": "2024-02-01", "end": "2024-03-01" },
57
+ { "name": "Frontend", "start": "2024-02-12", "end": "2024-03-08" }
58
+ ]
59
+ },
60
+ { "name": "Launch", "milestone": true, "date": "2024-04-01", "color": "#FF5757" }
61
+ ]
62
+ }
63
+ ```
64
+
65
+ ### 2. Generate the chart
66
+
67
+ **CLI:**
68
+
69
+ ```bash
70
+ jsonantt project.json project.png
71
+ jsonantt project.json project.pdf # vector PDF
72
+ jsonantt project.json project.svg # scalable SVG
73
+ jsonantt --dpi 300 project.json project.png # high-resolution PNG
74
+ ```
75
+
76
+ **Python API:**
77
+
78
+ ```python
79
+ from jsonantt import load_chart, render_chart
80
+
81
+ config = load_chart("project.json")
82
+ render_chart(config, "project.png", dpi=150)
83
+ ```
84
+
85
+ ---
86
+
87
+ ## JSON reference
88
+
89
+ | Field | Type | Description |
90
+ |-------|------|-------------|
91
+ | `title` | string | Optional chart title shown at the top |
92
+ | `dateformat` | string | `strptime` format string (default: `"%Y-%m-%d"`) |
93
+ | `start` | date string | Optional chart x-axis start date (overrides task dates) |
94
+ | `end` | date string | Optional chart x-axis end date |
95
+ | `style` | object | Visual style overrides (see below) |
96
+ | `tasks` | array | Top-level list of task objects |
97
+
98
+ ### Task object
99
+
100
+ | Field | Type | Description |
101
+ |-------|------|-------------|
102
+ | `name` | string | **Required.** Task label |
103
+ | `start` | date string | Bar start date |
104
+ | `end` | date string | Bar end date |
105
+ | `color` | CSS hex string | Bar/milestone colour (e.g. `"#4472C4"`) |
106
+ | `milestone` | boolean | Render as a diamond milestone instead of a bar |
107
+ | `date` | date string | Milestone date (used when `milestone: true`) |
108
+ | `children` | array | Nested sub-tasks (infinitely nestable) |
109
+
110
+ > **Auto date computation:** When a task has `children` but no explicit `start`/`end`, the dates are computed automatically as the earliest child start and latest child end, recursively.
111
+
112
+ ### Style object
113
+
114
+ | Field | Default | Description |
115
+ |-------|---------|-------------|
116
+ | `width` | `14` | Figure width in inches |
117
+ | `row_height` | `0.45` | Height of each task row in inches |
118
+ | `bar_height` | `0.5` | Bar height as a fraction of `row_height` |
119
+ | `font_size` | `9` | Base font size in points |
120
+ | `indent_size` | `3` | Spaces added per depth level in labels |
121
+ | `label_fraction` | `0.28` | Fraction of figure width used for labels |
122
+ | `colors` | palette | Array of default hex colours cycled per top-level task |
123
+ | `background` | `"#FFFFFF"` | Figure background colour |
124
+ | `grid_color` | `"#E0E0E0"` | Vertical gridline colour |
125
+ | `row_band_color` | `"#F5F5F5"` | Alternating row band colour |
126
+ | `milestone_color` | `"#E65100"` | Default milestone colour |
127
+
128
+ ---
129
+
130
+ ## Examples
131
+
132
+ See the [`examples/`](examples/) folder for ready-to-run JSON files:
133
+
134
+ - [`examples/simple.json`](examples/simple.json) — a three-phase project with milestones
135
+ - [`examples/complex.json`](examples/complex.json) — a multi-quarter roadmap with deep nesting and custom colours
136
+
137
+ Generate them locally:
138
+
139
+ ```bash
140
+ jsonantt examples/simple.json simple.png
141
+ jsonantt examples/complex.json complex.png
142
+ ```
143
+
144
+ ---
145
+
146
+ ## License
147
+
148
+ MIT
@@ -0,0 +1,18 @@
1
+ """jsonantt – Gantt chart generation from JSON descriptions.
2
+
3
+ Quick-start example::
4
+
5
+ from jsonantt import load_chart, render_chart
6
+
7
+ config = load_chart("project.json")
8
+ render_chart(config, "project.png")
9
+
10
+ Or from the command line::
11
+
12
+ jsonantt project.json project.png
13
+ """
14
+ from .parser import load_chart, parse_chart
15
+ from .renderer import render_chart
16
+
17
+ __all__ = ["load_chart", "parse_chart", "render_chart"]
18
+ __version__ = "0.1.0"
@@ -0,0 +1,49 @@
1
+ """Command-line interface for jsonantt."""
2
+ from __future__ import annotations
3
+
4
+ import argparse
5
+ import sys
6
+
7
+ from .parser import load_chart
8
+ from .renderer import render_chart
9
+
10
+
11
+ def main(argv=None) -> int:
12
+ parser = argparse.ArgumentParser(
13
+ prog="jsonantt",
14
+ description=(
15
+ "Generate a Gantt chart image from a JSON description.\n\n"
16
+ "Example:\n jsonantt project.json chart.png\n jsonantt project.json chart.pdf"
17
+ ),
18
+ formatter_class=argparse.RawDescriptionHelpFormatter,
19
+ )
20
+ parser.add_argument("input", help="Path to the JSON chart description file")
21
+ parser.add_argument("output", help="Output image path (e.g. chart.png, chart.pdf)")
22
+ parser.add_argument(
23
+ "--dpi", type=int, default=150,
24
+ help="Image resolution in DPI (default: 150, raster formats only)",
25
+ )
26
+
27
+ args = parser.parse_args(argv)
28
+
29
+ try:
30
+ config = load_chart(args.input)
31
+ except FileNotFoundError:
32
+ print(f"error: input file not found: {args.input}", file=sys.stderr)
33
+ return 1
34
+ except Exception as exc: # noqa: BLE001
35
+ print(f"error: failed to parse {args.input}: {exc}", file=sys.stderr)
36
+ return 1
37
+
38
+ try:
39
+ render_chart(config, args.output, dpi=args.dpi)
40
+ except Exception as exc: # noqa: BLE001
41
+ print(f"error: failed to render chart: {exc}", file=sys.stderr)
42
+ return 1
43
+
44
+ print(f"Chart saved to {args.output}")
45
+ return 0
46
+
47
+
48
+ if __name__ == "__main__":
49
+ sys.exit(main())
@@ -0,0 +1,129 @@
1
+ """Data models for jsonantt."""
2
+ from __future__ import annotations
3
+
4
+ from dataclasses import dataclass, field
5
+ from datetime import date
6
+ from typing import List, Optional
7
+
8
+
9
+ # ---------------------------------------------------------------------------
10
+ # Default colour palette (can be overridden in JSON "style" block)
11
+ # ---------------------------------------------------------------------------
12
+ DEFAULT_PALETTE: List[str] = [
13
+ "#4472C4", # steel blue
14
+ "#ED7D31", # orange
15
+ "#70AD47", # green
16
+ "#FF5757", # coral red
17
+ "#9DC3E6", # sky blue
18
+ "#FFC000", # amber
19
+ "#7030A0", # purple
20
+ "#00B0F0", # cyan
21
+ "#FF0066", # hot pink
22
+ "#00B050", # emerald
23
+ ]
24
+
25
+
26
+ @dataclass
27
+ class Arrow:
28
+ """A dependency arrow connecting the end of one task to the start of another."""
29
+
30
+ from_id: str
31
+ to_id: str
32
+ color: str = "#888888"
33
+ label: Optional[str] = None
34
+
35
+
36
+ @dataclass
37
+ class Task:
38
+ """A single task (or milestone) in the Gantt chart.
39
+
40
+ Date resolution modes
41
+ ---------------------
42
+ 1. ``start`` + ``end`` – explicit dates (original behaviour)
43
+ 2. ``start`` + ``duration`` – ``end`` computed from ``start`` + duration
44
+ 3. ``not_before`` + ``duration`` – ``start`` set to the ``effective_end``
45
+ of the task whose ``id`` matches ``not_before``, then ``end`` computed.
46
+
47
+ When absent the values are derived recursively from *children* via
48
+ :pymeth:`effective_start` / :pymeth:`effective_end`.
49
+ """
50
+
51
+ name: str
52
+ id: Optional[str] = None # unique identifier for cross-referencing
53
+ start: Optional[date] = None
54
+ end: Optional[date] = None
55
+ color: Optional[str] = None
56
+ milestone: bool = False
57
+ milestone_date: Optional[date] = None
58
+ children: List["Task"] = field(default_factory=list)
59
+ # -- deferred-resolution fields (set by parser, consumed during resolve) --
60
+ not_before: Optional[str] = None # id of task whose end becomes this start
61
+ duration_spec: Optional[str] = None # raw duration string e.g. "3m", "14d"
62
+ marker_size: Optional[float] = None # override milestone diamond size (pts)
63
+ bold: bool = False # render label in bold
64
+
65
+ # ------------------------------------------------------------------ #
66
+ # Computed properties #
67
+ # ------------------------------------------------------------------ #
68
+
69
+ @property
70
+ def effective_start(self) -> Optional[date]:
71
+ """Earliest start date, resolving through children if needed."""
72
+ if self.milestone:
73
+ return self.milestone_date or self.start
74
+ if self.start is not None:
75
+ return self.start
76
+ starts = [c.effective_start for c in self.children if c.effective_start is not None]
77
+ return min(starts) if starts else None
78
+
79
+ @property
80
+ def effective_end(self) -> Optional[date]:
81
+ """Latest end date, resolving through children if needed."""
82
+ if self.milestone:
83
+ return self.milestone_date or self.start
84
+ if self.end is not None:
85
+ return self.end
86
+ ends = [c.effective_end for c in self.children if c.effective_end is not None]
87
+ return max(ends) if ends else None
88
+
89
+ @property
90
+ def is_parent(self) -> bool:
91
+ """True when this task has sub-tasks."""
92
+ return bool(self.children)
93
+
94
+
95
+ @dataclass
96
+ class Style:
97
+ """Visual style configuration for the chart."""
98
+
99
+ width: float = 14.0 # figure width in inches
100
+ row_height: float = 0.3 # height of each row in inches
101
+ bar_height: float = 0.5 # bar height as fraction of row_height
102
+ font_size: float = 12.0 # base font size in pts
103
+ indent_size: int = 3 # spaces added per depth level
104
+ label_fraction: float = 0.28 # fraction of figure width used for labels
105
+ colors: List[str] = field(default_factory=lambda: list(DEFAULT_PALETTE))
106
+ background: str = "#FFFFFF" # figure background colour
107
+ grid_color: str = "#E0E0E0" # vertical gridline colour
108
+ row_band_color: str = "#F5F5F5" # alternating row band colour
109
+ milestone_color: str = "#E65100" # default milestone colour
110
+ milestone_size: float = 14.0 # default milestone diamond size (pts)
111
+ major_tick: Optional[str] = None # e.g. "year", "quarter", "month", "week"
112
+ minor_tick: Optional[str] = None # e.g. "quarter", "month", "week", "day"
113
+ major_grid_width: float = 2.0 # major gridline linewidth
114
+ minor_grid_width: float = 1.5 # minor gridline linewidth
115
+ bold_tasks: bool = True # auto-bold top-level (depth 0) tasks
116
+ tick_position: str = "top" # x-axis label position: "top", "bottom", or "both"
117
+
118
+
119
+ @dataclass
120
+ class ChartConfig:
121
+ """Top-level chart configuration parsed from JSON."""
122
+
123
+ tasks: List[Task] = field(default_factory=list)
124
+ title: str = ""
125
+ date_format: str = "%Y-%m-%d"
126
+ start: Optional[date] = None # force chart x-axis start
127
+ end: Optional[date] = None # force chart x-axis end
128
+ style: Style = field(default_factory=Style)
129
+ arrows: List[Arrow] = field(default_factory=list)