generated from coulomb/repo-seed
Added optional placeholderMapping to project.json for templates that
don't follow the standard ITEM_{PROPERTY} convention.
Features:
- Override default placeholder names per field
- Useful for legacy templates or migration from other tools
- Fallback to ITEM_{PROPERTY} convention when not specified
Example usage:
{
"fieldMapping": {
"id": "ID",
"title": "Title"
},
"placeholderMapping": {
"id": "TASK_ID", // Use {{TASK_ID}} instead of {{ITEM_ID}}
"title": "TASK_NAME" // Use {{TASK_NAME}} instead of {{ITEM_TITLE}}
}
}
Changes:
- generator.js: Check for cfg.placeholderMapping before using default
- Added 2 new tests for custom mapping behavior
- Updated TEMPLATE_V2_GUIDE.md with documentation and examples
All 58 tests passing.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
355 lines
12 KiB
Markdown
355 lines
12 KiB
Markdown
# Template-v2.svg Guide
|
|
|
|
## Overview
|
|
|
|
Template-v2.svg is the required format for creating custom timeline visualizations. Templates are standard SVG files that can be edited in any SVG editor (Inkscape, Adobe Illustrator, Figma, etc.) and contain special template elements that define how months, lanes, and tasks are rendered.
|
|
|
|
## Template Structure
|
|
|
|
A valid template-v2.svg file must contain:
|
|
|
|
1. **Standard SVG wrapper** with xmlns declaration
|
|
2. **`<defs>` section** containing three required template elements:
|
|
- `<g id="month-template">` - Defines how each month column is rendered
|
|
- `<g id="lane-template">` - Defines how each lane (epic/swimlane) is rendered
|
|
- `<g id="item-template">` - Defines how each task item is rendered
|
|
3. **Main content area** with `{{MONTHS}}` and `{{LANES}}` placeholders
|
|
4. **Optional styling** (gradients, filters, patterns, etc.)
|
|
|
|
### Minimal Template Example
|
|
|
|
```svg
|
|
<svg xmlns="http://www.w3.org/2000/svg">
|
|
<defs>
|
|
<!-- Month template -->
|
|
<g id="month-template" style="display:none">
|
|
<line x1="{{MONTH_X}}" y1="{{GRID_TOP}}" x2="{{MONTH_X}}" y2="{{GRID_BOTTOM}}"
|
|
stroke="#E0E0E0" stroke-width="1"/>
|
|
<text x="{{MONTH_TEXT_X}}" y="{{MONTH_LABEL_Y}}"
|
|
font-family="Arial" font-size="12" fill="#424242">{{MONTH_LABEL}}</text>
|
|
</g>
|
|
|
|
<!-- Lane template -->
|
|
<g id="lane-template" style="display:none">
|
|
<rect x="{{LANE_X}}" y="{{LANE_Y}}" width="{{LANE_WIDTH}}" height="{{LANE_HEIGHT}}"
|
|
fill="#FAFAFA" stroke="#E0E0E0" rx="8"/>
|
|
<text x="{{LABEL_X}}" y="{{LABEL_Y}}"
|
|
font-family="Arial" font-size="14" font-weight="bold" fill="#212121">{{LANE_NAME}}</text>
|
|
</g>
|
|
|
|
<!-- Item template -->
|
|
<g id="item-template" style="display:none">
|
|
<circle cx="{{ITEM_X}}" cy="{{ITEM_Y}}" r="6" fill="#1976D2"/>
|
|
<text x="{{TEXT_X}}" y="{{TEXT_Y}}"
|
|
font-family="Arial" font-size="11" fill="#424242">{{ITEM_ID}} {{ITEM_TITLE}}</text>
|
|
</g>
|
|
</defs>
|
|
|
|
<rect width="100%" height="100%" fill="#FFFFFF"/>
|
|
{{MONTHS}}
|
|
{{LANES}}
|
|
</svg>
|
|
```
|
|
|
|
## Available Placeholders
|
|
|
|
### Month Template Placeholders
|
|
|
|
| Placeholder | Description | Example Value |
|
|
|------------|-------------|---------------|
|
|
| `{{MONTH_X}}` | X position of month column | 340 |
|
|
| `{{GRID_TOP}}` | Y position of grid start | 120 |
|
|
| `{{GRID_BOTTOM}}` | Y position of grid end | 560 |
|
|
| `{{MONTH_X_OFFSET}}` | X position offset for backgrounds | 310 |
|
|
| `{{MONTH_LABEL_Y_OFFSET}}` | Y position offset for label background | 70 |
|
|
| `{{MONTH_TEXT_X}}` | X position for month text | 344 |
|
|
| `{{MONTH_LABEL_Y}}` | Y position for month text | 90 |
|
|
| `{{MONTH_LABEL}}` | Month label text | "Jan 25" |
|
|
| `{{MONTH_SEP_X}}` | X position for separator line | 339 |
|
|
| `{{GRID_HEIGHT}}` | Height of the grid | 440 |
|
|
|
|
### Lane Template Placeholders
|
|
|
|
| Placeholder | Description | Example Value |
|
|
|------------|-------------|---------------|
|
|
| `{{LANE_X}}` | X position of lane | 40 |
|
|
| `{{LANE_Y}}` | Y position of lane | 116 |
|
|
| `{{LANE_WIDTH}}` | Width of lane | 2460 |
|
|
| `{{LANE_HEIGHT}}` | Height of lane | 80 |
|
|
| `{{LABEL_X}}` | X position for lane label | 56 |
|
|
| `{{LABEL_Y}}` | Y position for lane label | 140 |
|
|
| `{{LANE_NAME}}` | Lane name text (XML-escaped) | "Development" |
|
|
|
|
### Item Template Placeholders
|
|
|
|
| Placeholder | Description | Example Value |
|
|
|------------|-------------|---------------|
|
|
| `{{ITEM_X}}` | X position of item marker | 400 |
|
|
| `{{ITEM_Y}}` | Y position of item marker | 150 |
|
|
| `{{TEXT_X}}` | X position for item text | 412 |
|
|
| `{{TEXT_Y}}` | Y position for item text | 154 |
|
|
| `{{ITEM_ID}}` | Item ID (XML-escaped) | "T-123" |
|
|
| `{{ITEM_TITLE}}` | Item title (XML-escaped) | "Implement feature" |
|
|
|
|
**Dynamic Data Placeholders:**
|
|
|
|
The generator automatically creates placeholders for **all properties** in your CSV data using the naming convention: `ITEM_{PROPERTY_UPPERCASE}`.
|
|
|
|
For example, if your `fieldMapping` includes:
|
|
```json
|
|
{
|
|
"id": "ID",
|
|
"title": "Title",
|
|
"assignee": "Assignee",
|
|
"priority": "Priority"
|
|
}
|
|
```
|
|
|
|
The following placeholders become available:
|
|
- `{{ITEM_ID}}` - from the `id` field
|
|
- `{{ITEM_TITLE}}` - from the `title` field
|
|
- `{{ITEM_ASSIGNEE}}` - from the `assignee` field
|
|
- `{{ITEM_PRIORITY}}` - from the `priority` field
|
|
|
|
**Note:** The `due` field is used for positioning and is not available as a placeholder (use `{{MONTH_LABEL}}` for date display).
|
|
|
|
**Custom Placeholder Mapping:**
|
|
|
|
If your template uses non-standard placeholder names, you can override the default convention using `placeholderMapping` in project.json:
|
|
|
|
```json
|
|
{
|
|
"fieldMapping": {
|
|
"id": "ID",
|
|
"title": "Title",
|
|
"assignee": "Assignee"
|
|
},
|
|
"placeholderMapping": {
|
|
"id": "TASK_ID", // Use {{TASK_ID}} instead of {{ITEM_ID}}
|
|
"title": "TASK_NAME", // Use {{TASK_NAME}} instead of {{ITEM_TITLE}}
|
|
"assignee": "ASSIGNEE" // Use {{ASSIGNEE}} instead of {{ITEM_ASSIGNEE}}
|
|
}
|
|
}
|
|
```
|
|
|
|
This is useful when:
|
|
- Working with existing templates that use different naming conventions
|
|
- Migrating from other timeline tools
|
|
- Maintaining compatibility with legacy templates
|
|
|
|
Without `placeholderMapping`, the default `ITEM_{PROPERTY}` convention is used.
|
|
|
|
### Global Placeholders
|
|
|
|
These appear in the main template body (not in template elements):
|
|
|
|
| Placeholder | Description |
|
|
|------------|-------------|
|
|
| `{{MONTHS}}` | Replaced with all rendered month elements |
|
|
| `{{LANES}}` | Replaced with all rendered lane and task elements |
|
|
|
|
## Editing Templates in SVG Tools
|
|
|
|
### Inkscape
|
|
|
|
1. Open template-v2.svg in Inkscape
|
|
2. Locate template elements in the Layers panel (inside `<defs>`)
|
|
3. Edit shapes, colors, fonts, etc. as needed
|
|
4. **Important**: Keep `id="month-template"`, `id="lane-template"`, `id="item-template"` unchanged
|
|
5. **Important**: Keep `{{PLACEHOLDER}}` text exactly as is - these are replaced at runtime
|
|
6. Save file (keep SVG format, avoid Inkscape-specific extensions)
|
|
|
|
### Adobe Illustrator
|
|
|
|
1. Open template-v2.svg in Illustrator
|
|
2. Use Layers panel to find template groups
|
|
3. Edit visual properties (stroke, fill, fonts)
|
|
4. **Do not** change group IDs or placeholder text
|
|
5. Export as SVG (use "SVG 1.1" profile)
|
|
|
|
### Figma
|
|
|
|
1. Import template-v2.svg into Figma
|
|
2. Edit styling and layout
|
|
3. Keep placeholder text intact (wrapped in `{{` and `}}`)
|
|
4. Export as SVG
|
|
5. Manually verify `id` attributes are preserved
|
|
|
|
## Common Customizations
|
|
|
|
### Changing Colors
|
|
|
|
Edit the `fill` and `stroke` attributes:
|
|
|
|
```svg
|
|
<!-- Original -->
|
|
<rect fill="#FAFAFA" stroke="#E0E0E0"/>
|
|
|
|
<!-- Dark theme -->
|
|
<rect fill="#2C2C2C" stroke="#404040"/>
|
|
```
|
|
|
|
### Changing Fonts
|
|
|
|
Edit `font-family`, `font-size`, and `font-weight` attributes:
|
|
|
|
```svg
|
|
<!-- Original -->
|
|
<text font-family="Arial" font-size="12" fill="#424242">{{MONTH_LABEL}}</text>
|
|
|
|
<!-- Custom font -->
|
|
<text font-family="'Roboto', sans-serif" font-size="14" fill="#1A1A1A">{{MONTH_LABEL}}</text>
|
|
```
|
|
|
|
### Adding Gradients
|
|
|
|
1. Define gradient in `<defs>`:
|
|
|
|
```svg
|
|
<defs>
|
|
<linearGradient id="laneGradient" x1="0%" y1="0%" x2="0%" y2="100%">
|
|
<stop offset="0%" style="stop-color:#E3F2FD;stop-opacity:1" />
|
|
<stop offset="100%" style="stop-color:#BBDEFB;stop-opacity:1" />
|
|
</linearGradient>
|
|
|
|
<g id="lane-template" style="display:none">
|
|
<rect x="{{LANE_X}}" y="{{LANE_Y}}" width="{{LANE_WIDTH}}" height="{{LANE_HEIGHT}}"
|
|
fill="url(#laneGradient)" stroke="#90CAF9" rx="8"/>
|
|
...
|
|
</g>
|
|
</defs>
|
|
```
|
|
|
|
### Adding Drop Shadows
|
|
|
|
Use SVG filters:
|
|
|
|
```svg
|
|
<defs>
|
|
<filter id="dropShadow" x="-50%" y="-50%" width="200%" height="200%">
|
|
<feGaussianBlur in="SourceAlpha" stdDeviation="2"/>
|
|
<feOffset dx="0" dy="2" result="offsetblur"/>
|
|
<feComponentTransfer>
|
|
<feFuncA type="linear" slope="0.2"/>
|
|
</feComponentTransfer>
|
|
<feMerge>
|
|
<feMergeNode/>
|
|
<feMergeNode in="SourceGraphic"/>
|
|
</feMerge>
|
|
</filter>
|
|
|
|
<g id="item-template" style="display:none">
|
|
<circle cx="{{ITEM_X}}" cy="{{ITEM_Y}}" r="6" fill="#1976D2" filter="url(#dropShadow)"/>
|
|
...
|
|
</g>
|
|
</defs>
|
|
```
|
|
|
|
## Layout Constants
|
|
|
|
The generator uses these constants for positioning (defined in generator.js):
|
|
|
|
```javascript
|
|
const left = 220; // Left margin
|
|
const top = 140; // Top margin
|
|
const monthWidth = 120; // Width of each month column
|
|
const laneHeight = 80; // Height of each lane
|
|
const laneGap = 16; // Vertical gap between lanes
|
|
```
|
|
|
|
To change the overall layout, you would need to modify generator.js. Templates control the visual appearance within these constraints.
|
|
|
|
## Troubleshooting
|
|
|
|
### Error: "Template is required"
|
|
|
|
**Cause**: No template was provided or template file couldn't be loaded.
|
|
|
|
**Fix**:
|
|
- Verify `svgTemplate` field in project.json points to a valid template-v2.svg file
|
|
- Check that the template file exists in the project folder
|
|
|
|
### Error: "Template is missing required elements: month-template"
|
|
|
|
**Cause**: The template is missing one or more required `<g id="*-template">` elements.
|
|
|
|
**Fix**:
|
|
- Ensure template has all three required elements in `<defs>`:
|
|
- `<g id="month-template">`
|
|
- `<g id="lane-template">`
|
|
- `<g id="item-template">`
|
|
- Check that IDs are exactly as shown (case-sensitive)
|
|
- Verify elements are inside `<defs>` section
|
|
|
|
### Error: "Failed to extract template element: month-template"
|
|
|
|
**Cause**: Template element was found during validation but couldn't be extracted (malformed structure).
|
|
|
|
**Fix**:
|
|
- Ensure each template element is properly closed: `<g id="month-template">...</g>`
|
|
- Check for invalid nested structures or unclosed tags
|
|
- Validate SVG syntax using an online SVG validator
|
|
|
|
### Placeholders Not Being Replaced
|
|
|
|
**Symptoms**: Generated SVG contains literal `{{MONTH_X}}` text instead of numbers.
|
|
|
|
**Fix**:
|
|
- Verify placeholder syntax: must be exactly `{{PLACEHOLDER}}` (case-sensitive)
|
|
- Check for typos in placeholder names (see tables above for correct names)
|
|
- Ensure placeholders are in text content, not in comments
|
|
|
|
### Template Elements Visible in Output
|
|
|
|
**Symptoms**: Template elements appear in the final SVG with `display:none` style.
|
|
|
|
**Fix**:
|
|
- Verify that template elements have `style="display:none"` attribute
|
|
- The generator removes this attribute when cloning, but if elements are leaking through, check template structure
|
|
- Ensure template elements are only in `<defs>`, not in main content area
|
|
|
|
### SVG Dimensions Too Small/Large
|
|
|
|
**Cause**: Generator calculates dimensions based on number of months, lanes, and layout constants.
|
|
|
|
**Symptoms**: Content is cut off or timeline has excessive white space.
|
|
|
|
**Fix**:
|
|
- Adjust `timelineMonths` setting in project.json to show more/fewer months
|
|
- Modify layout constants in generator.js:151 for more control over spacing
|
|
- Template styling doesn't affect dimensions - that's controlled by the generator
|
|
|
|
## Best Practices
|
|
|
|
1. **Keep template elements hidden**: Always include `style="display:none"` on template groups
|
|
2. **Use semantic IDs**: Don't change the required `id` attributes
|
|
3. **Test frequently**: Generate a timeline after each template edit to verify changes
|
|
4. **Version control**: Keep template files in version control to track changes
|
|
5. **Start simple**: Begin with the minimal template example and add complexity gradually
|
|
6. **Preserve placeholders**: Never modify the placeholder text - they're replaced at runtime
|
|
7. **Use relative units carefully**: Absolute pixel values work best for positioning placeholders
|
|
8. **Add comments**: Document custom styles and modifications for future reference
|
|
|
|
## Example Templates
|
|
|
|
See the `example/` and `my-project/` folders for working template-v2.svg files that demonstrate:
|
|
- Professional styling with gradients and shadows
|
|
- Custom color schemes
|
|
- Different font choices
|
|
- Grid patterns and backgrounds
|
|
|
|
## Further Resources
|
|
|
|
- **SVG Specification**: https://www.w3.org/TR/SVG2/
|
|
- **Inkscape Tutorials**: https://inkscape.org/learn/tutorials/
|
|
- **SVG Filters**: https://www.w3.org/TR/SVG11/filters.html
|
|
- **Online SVG Editor**: https://svg-edit.github.io/svgedit/
|
|
|
|
## Support
|
|
|
|
If you encounter issues not covered in this guide:
|
|
1. Check that your template validates as proper SVG
|
|
2. Compare with the example templates
|
|
3. Review error messages carefully - they indicate what's missing or malformed
|
|
4. File an issue at the project repository with your template attached
|