A command-line tool that reads a GEDCOM 5.5.1 genealogical file and generates a family-tree chart as text, SVG, or PDF.
Version: v0.9.0
# Install from source
cargo install --path .
# Or build a release binary in target/release/
cargo build --releaseRequires a stable Rust toolchain (Rust 2024 edition).
genechart [OPTIONS] [GEDCOM_FILE]
| Flag | Description |
|---|---|
-r / --root <ID> |
Root individual ID (default: first individual in file) |
-g <N> / --generations <N> / --gen <N> |
Number of generations to show |
--dir <DIRECTION> |
Chart direction: descendants, ancestors = pedigree, forest |
--type <TYPE> |
Layout algorithm: simple, fan, boxed_couples, fancy, boxes |
--text |
Output as plain text |
--svg |
Output as SVG |
--pdf |
Output as PDF |
-o / --output <FILE> |
Output file (extension infers type if no --text/--svg/--pdf flag) |
--merge <GEDCOM> <ALIAS> |
Merge a further GEDCOM file using an alias file (repeatable; order matters) |
--pref '<key name="val">' |
Override any preference inline (TOML syntax, repeatable) |
--pref |
Bare --pref (no value): dump merged preferences to stdout and exit |
--prpref |
Print the fully-resolved preferences as TOML and exit; combine with -o to see type inference |
--preff <FILE> |
Load an explicit TOML preferences file (repeatable; files apply in order, a later file overriding an earlier one) |
--trace [COMPONENT] |
Print structured diagnostics to stderr; bare --trace traces all |
-h / --help |
Show help |
--version |
Show version |
# Generate a 4-generation descendant chart as SVG
genechart family.ged -r I1 -g 4 --svg -o chart.svg
# Generate a pedigree fan chart as PDF (type inferred from extension)
genechart family.ged -r I1 --type fan -o chart.pdf
# Ancestor chart, plain text, 3 generations
genechart family.ged -r I1 --dir ancestors -g 3 --text
# Boxed couples layout as SVG
genechart family.ged -r I1 --type boxed_couples -o chart.svg
# Dump merged preferences (useful for debugging)
genechart family.ged --pref
# Print resolved preferences for SVG output (no chart generated)
genechart family.ged -r I1 --prpref -o chart.svg
# Trace how preferences are resolved across all sources
genechart family.ged -r I1 -g 5 --trace prefs 2>&1 | head
# Use a shared preferences file for a project-specific style
genechart family.ged --preff ~/projects/genealogy/style.toml
# Merge a second GEDCOM (paternal ancestry) into the main file
genechart maternal.ged -r I1 --merge paternal.ged mat_to_pat_aliases.txt --dir ancestors -o chart.svgText-like layout with indented generations. Suitable for terminal output or simple SVG/PDF charts. Supports descendants, ancestors, and forest directions.
In forest mode every individual is in scope and each disconnected family tree is rendered as an independent sub-chart below the previous one (largest tree first). Sub-trees whose children were already shown in a prior tree are replaced by ... to avoid repetition. With show.last_gen_spouses = true, redundant spouse-only trees (the couple already appeared in a larger tree and has no new children) are suppressed entirely.
genechart family.ged -r I1 --type simple -o chart.svg
genechart family.ged --dir forest --pref 'show.last_gen_spouses = true' --textConfiguration: [layout.simple] — indent (columns per generation), vert_spacing (lines between generations).
When show.notes = true, GEDCOM NOTE text is rendered as additional indented rows below each individual (simple layout only; descendants, ancestors, and forest directions). When show.notes_html = true, HTML anchor tags inside notes (<a href="…">text</a>) are rendered as clickable hyperlinks in SVG and PDF output; other HTML tags have their content shown as plain text.
Recursive box-placement algorithm. Each individual or couple gets a box; children are placed below their parents with envelope-based spacing to avoid overlaps. Supports descendants and ancestors directions.
genechart family.ged -r I1 --type boxed_couples -o chart.svgConfiguration: [layout.boxed_couples] — box_width, box_height, gap_width, gap_height, box_width_2_spouses.
Name autocompress: in the boxes and boxed_couples layouts, a name too wide for its box is compressed horizontally (narrower glyphs, same height) down to output.style.spacing.names_autocompress (default 0.85; set >= 1.0 to disable). It shrinks only as much as needed to fit; if even the floor isn't enough it shrinks to the floor and overflows anyway. With diagnostics.info enabled, each compressed name logs its id, name, and effective percentage; with diagnostics.warnings enabled, any name that still doesn't fit logs an overflow percentage.
Realistic tree branches (experimental, work in progress): the boxed_couples layout
can optionally replace its straight connectors with organic-looking tree branches. This
feature is still rough and its output is not yet satisfactory — see
docs/realistic-tree.md for styles and configuration.
Half-circle pedigree fan (180°). Places ancestors in concentric rings, with the root at the center. Ancestors-only.
genechart family.ged -r I1 --type fan -o chart.svgConfiguration: [layout.fan] — ring_height (radial thickness of each inner ring), ring_gap (gap between rings), outer_ring_height (radial thickness of outer radial-text rings), radial_gen (generation index at which to switch to radial text; 0 = all radial).
Wedge appearance is styled via [output.style.wedges] — width (stroke width), border (stroke color), background (fill color) — with the same meaning as [output.style.boxes]. Colors accept transparency (see Colors and transparency).
Cascading layout (SVG/PDF only). Supports both descendants and ancestors directions. In descendants mode, each direct descendant is left-aligned by generation at a fixed horizontal distance, with spouses listed below each individual and children branching to the right via curved connectors. In ancestors mode, the root is at the left and ancestors grow rightward, one column per generation.
genechart family.ged -r I1 --type fancy -o chart.svg
genechart family.ged -r I1 --type fancy --dir ancestors -o chart.svgConfiguration: [layout.fancy] — gen_width (horizontal distance between successive generations; ignored when compact = true), child_gap (vertical gap between a person's last spouse and their first child), anc_gap (vertical breathing room around each individual in ancestors mode), compact (default true: children are stacked below the spouse column rather than placed gen_width to the right, producing a much narrower chart).
One individual per box (SVG/PDF only). No marriage data is shown. Supports both descendants and ancestors directions. Individuals reachable via more than one path (endogamy) are placed at every occurrence — see Reconvergent trees.
Ancestors direction: the root is at one edge and parents grow outward one box per generation. Father boxes are placed to the left, mother boxes to the right. A horizontal connector bar links each individual to their parents.
Descendants direction: the root is at one edge and each individual has their own box. Spouses are placed to the right of the individual, slightly lower (couple_y_offset). Children of each spouse are placed below that spouse, centered under it.
genechart family.ged -r I1 --type boxes --dir ancestors -o chart.svg
genechart family.ged -r I1 --type boxes -o chart.svgConfiguration: [layout.boxes] — box_width, box_height, gap_width, gap_height, couple_y_offset (vertical offset between individual and spouse box tops, descendants only).
Photos: the boxes layout optionally displays a photo at the top of each box, above the name. Enable with show.photo = true and configure the [photos] section. Photos are not supported in other layout types.
Plain text with column-aligned names, dates, and dot leaders. Default output format.
genechart family.ged -r I1 --textVector graphics output. Supports boxes, connectors, and configurable fonts. Every SVG element carries a class= attribute so the chart can be restyled with a <style> block or external CSS without editing the source TOML:
| Element | Class(es) |
|---|---|
Individual/couple boxes (<rect>) |
box; two-spouse boxes also get double |
Connectors (<line>, <path>) |
connector |
Fan wedges (<path>) |
wedge |
Row-rule underlines (<line>) |
row_rule |
Note bars (<line>) |
note_bar |
Note hyperlinks (<a>) |
note_link |
Highlight backgrounds (<rect>) |
highlight_rect |
Photo images (<image>) |
photo |
Realistic tree layer (<g>) |
realistic-tree |
| Realistic tree branch paths/lines | tree-branch |
| Realistic tree leaf shapes | tree-leaf |
| Text elements | indi_name, spouse_name, indi_birth, indi_death, indi_marriage, indi_id, gen_num, note_text; highlighted text also adds highlighted |
genechart family.ged -r I1 --svg -o chart.svgGenerated via SVG conversion. Supports poster tiling (multi-page output for large charts).
genechart family.ged -r I1 --pdf -o chart.pdfPoster tiling: configure [output.poster] — rows, columns, overlap_mm, alignment_lines.
Generate a 2x2 poster from a sample GEDCOM file:
# Tiled 2x2 poster via command-line overrides
genechart family.ged -r I1 --type boxed_couples --pdf -o chart.pdf \
--pref 'output.poster.rows = 2' \
--pref 'output.poster.columns = 2' \
--pref 'output.poster.overlap_mm = 5.0'
# Or via a preferences file
genechart family.ged -r I1 --type boxed_couples --pdf -o chart.pdf \
--preff poster_style.tomlwhere poster_style.toml contains:
[output.poster]
rows = 2
columns = 2
overlap_mm = 5.0
alignment_lines = truePreferences are read from (lowest to highest priority):
- Installation-directory defaults (
defaults.toml) - User home (
~/.genechart.toml) - Directory TOML (
genechart.tomlin the same directory as the GEDCOM file) - File TOML (same basename as the GEDCOM file, e.g.
family.tomlforfamily.ged) --preff <FILE>— explicit preferences file(s); may be given more than once and are applied in command-line order, so a later--preffoverrides conflicting preferences from an earlier one--prefcommand-line overrides
Unknown preference keys in --pref are a hard error (the command aborts with a message naming the bad key). Unknown keys in TOML config files produce a warning and are ignored.
[files]
gedcom = "{gedcom}"
highlights = ""
merge = [] # further GEDCOM files to merge: ["paternal.ged", "maternal.ged", ...]
merge_aliases = [] # alias files, one per merge entry: ["aliases_pat.txt", "aliases_mat.txt", ...]
[scope]
root = ""
generations = 4
direction = "descendants"
[show]
generation_num = true
nickname = false # format individuals who have a GEDCOM NICK with format.individual_nickname
sex = true
birth = true
death = true
marriage = true
notes = false # simple layout: display GEDCOM NOTE text below each individual
notes_html = false # notes only: render <a href="..."> as clickable hyperlinks (SVG/PDF)
last_gen_spouses = false
id = false
photo = false
exclude = [] # prune branches; see "Excluding branches" below
missing_data = false # placeholder text for missing name/date/location; see "Missing data placeholders" below
[show.reconvergent] # individuals repeated via endogamy; see "Reconvergent trees" below
duplicate = true
reference = "" # "" | "123" | "abc" | "ABC"
[show.assume_deceased] # see "Missing data placeholders" below; only used when missing_data = true
birth_year_max = 1925
marriage_year_max = 1945
[photos]
directory = "photos" # relative to GEDCOM file
index = "" # "" = ID-based filenames (e.g. I1.jpg); non-empty = path to index file
embedded = false # true: base64 data URI; false: relative path; PDF always embeds
width = 100.0 # canvas units
height = 100.0 # canvas units
margin = 2.0 # space on all four sides of the photo within the box
scale = "crop" # "fit" | "crop" | "none"
box_resize = true # grow box height by (height + 2*margin) to fit the photo
downsample = 72.0 # max DPI for embedded images; 0.0 = no downsampling
[format]
individual = "{firstname} {lastname} {sex}"
individual_nickname = "{firstname} \"{nickname}\" {lastname} {sex}" # used when show.nickname and a NICK exists
birth = "* {date:%d %b %Y}, {location}"
death = "× {date:%d %b %Y}, {location}"
marriage = "⚭ {date:%d %b %Y}, {location}"
date_qualifiers = "compact" # "none" | "gedcom" | "compact"
no_name = "" # Placeholder for individuals with no name in the GEDCOM file; empty = omit the name line
[layout]
type = "simple"
root_pos = "bottom"
[layout.simple]
indent = 3
vert_spacing = 0
[layout.boxed_couples]
box_width = 240.0
box_height = 140.0
spouse_sep_height = 30.0
gap_width = 40.0
gap_height = 80.0
box_width_2_spouses = 520.0
[layout.fan]
ring_height = 90
ring_gap = 10
outer_ring_height = 200
radial_gen = 3
[layout.fancy]
gen_width = 300.0 # ignored when compact = true
child_gap = 10.0
anc_gap = 10.0
compact = true # stack children below spouse (narrower chart)
[layout.boxes]
box_width = 240.0
box_height = 80.0
gap_width = 40.0
gap_height = 80.0
couple_y_offset = 20.0
[output]
type = "text"
path = ""
noclobber = false
[output.paper]
size = "A4" # "A0"–"A5", "letter", "custom"
orientation = "portrait" # ignored when size = "custom"
[output.paper.custom]
width = 0.0 # mm — set both > 0 to activate
height = 0.0 # mm
[output.poster]
rows = 1
columns = 1
overlap_mm = 0.0
alignment_lines = true
[output.style]
dot_leaders = true
[output.style.boxes]
width = 0.5
border = 0x222 # colours are hex: 0xRGB / 0xRGBA / 0xRRGGBB / 0xRRGGBBAA (alpha-last)
background = 0xFFF
radius = 0.0
[output.style.wedges] # fan-layout wedges; same keys/meaning as [output.style.boxes]
width = 0.5
border = 0x222
background = 0xFFF # e.g. 0xFFF8 → ~53%-opaque white (4-digit hex = RGBA)
[output.style.spacing]
title = 12.0 # Vertical space (canvas units) between the title text and the chart
copyright = 12.0 # Vertical space (canvas units) between the copyright text and the chart
names_autocompress = 0.85 # boxes/boxed_couples: compress over-long names horizontally to
# at most this X-fraction so they fit the box; >= 1.0 disables it
[output.style.fonts]
names = "Georgia 14"
dates = "Arial 10"
title = "Georgia 24"
copyright = "Arial 8"
[output.style.text] # text colours (hex; alpha supported). 0x000 = opaque black.
names = 0x000 # individual & spouse names
dates = 0x000 # birth / death / marriage lines
gen_numbers = 0x000 # generation-number prefix (simple layout)
notes = 0x000 # GEDCOM note lines
id = 0xE00 # the ID column (when show.id = true)
title = 0x000 # chart title
copyright = 0x000 # copyright notice
row_rule = 0xCCC # row-rule underlines (simple layout)
note_bar = 0xCCC # vertical bar beside a note block
note_link = 0x06C # note hyperlinks (#0066CC)
reconvergent_reference = 0x084 # reference glyph on repeated individuals; see "Reconvergent trees" below
missing_data = 0xE00 # placeholder text only; see "Missing data placeholders" below
[output.text]
title = "{gedcom}"
copyright = ""
[output.text.missing] # placeholder strings; see "Missing data placeholders" below
name = "name?"
firstname = "firstname?"
lastname = "lastname?"
date = "date?"
location = "location?"Format strings use {key} placeholders: {firstname}, {lastname}, {sex}, {date}, {location}. See Date Formatting below for date pattern syntax.
Nicknames: when show.nickname = true, any individual with a non-empty GEDCOM NICK (read from 2 NICK under the NAME line, or a flat 1 NICK) is rendered with format.individual_nickname instead of format.individual. That template additionally supports the {nickname} placeholder. Individuals without a nickname are always formatted with format.individual.
Every color preference is a hex integer, written widest-meaningful-first:
| Form | Meaning |
|---|---|
0xRGB (3 digits) |
fully opaque, e.g. 0x222 |
0xRGBA (4 digits) |
RGB + alpha-last, e.g. 0xF008 = red at ~53% opacity, 0xFFFF = opaque white |
0xRRGGBB (6 digits) |
fully opaque, full 24-bit, e.g. 0x224466 |
0xRRGGBBAA (8 digits) |
24-bit RGB + alpha, e.g. 0x22446680 |
Transparency works in both SVG and PDF output and applies to any color preference:
box/wedge border & background, connectors, the text colors (names, dates, generation numbers,
notes, id, title, copyright), the row-rule underline, note bar, note hyperlink, highlight color
& background, and poster alignment lines. Limitation: a color whose most-significant hex digit
is 0 is indistinguishable from a shorter form (e.g. a translucent black cannot be
expressed), so keep a non-zero leading digit.
Dates (birth, death, marriage) have two independent controls:
Use {date:FORMAT} in a format template, where FORMAT is a strftime-like pattern:
| Code | Meaning | Example (1 JAN 1812) |
|---|---|---|
%d |
Day, zero-padded | 01 |
%e |
Day, no padding | 1 |
%m |
Month number, zero-padded | 01 |
%b |
Abbreviated month name | Jan |
%B |
Full month name | January |
%Y |
4-digit year | 1812 |
%y |
2-digit year | 12 |
Missing components (e.g. a GEDCOM date that has only a year) are silently omitted so {date:%d %b %Y} on "1812" produces "1812", not " 1812".
Plain {date} (without a colon) passes the raw GEDCOM string through — useful with date_qualifiers = "gedcom".
Controls how GEDCOM date qualifier tokens (ABT, BEF, AFT, BET…AND, FROM…TO) are rendered:
| Value | Behaviour |
|---|---|
"compact" (default) |
Translate to compact symbols (see table below) |
"none" |
Strip all qualifiers; for date ranges, show only the first date |
"gedcom" |
Pass through the raw GEDCOM string unchanged |
Compact symbol mapping:
| GEDCOM qualifier | Compact output |
|---|---|
ABT, CAL, EST |
~date |
BEF |
<date |
AFT |
>date |
BET … AND …, FROM … TO … |
date1-date2 |
Corner case — same-range deduplication: if both dates in a range format to the same string (e.g. {date:%Y} applied to "BET APR 1880 AND JUL 1880" yields 1880 for both), the qualifier is dropped and the date is shown once.
[format]
# Default: full date, compact qualifiers
birth = "* {date:%d %b %Y}, {location}"
date_qualifiers = "compact"| GEDCOM date | Output |
|---|---|
1 JAN 1812 |
* 01 Jan 1812 |
ABT 1850 |
* ~1850 |
BEF 1900 |
* <1900 |
AFT 1800 |
* >1800 |
BET APR 1880 AND JUL 1890 |
* Apr 1880-Jul 1890 |
BET APR 1880 AND JUL 1880 |
* Apr 1880 (deduplication) |
# Year-only, no qualifiers
birth = "* {date:%Y}, {location}"
date_qualifiers = "none"| GEDCOM date | Output |
|---|---|
ABT 1850 |
* 1850 |
BET 1880 AND 1890 |
* 1880 (first date only) |
# Raw GEDCOM pass-through
birth = "* {date}, {location}"
date_qualifiers = "gedcom"| GEDCOM date | Output |
|---|---|
ABT 1850 |
* ABT 1850 |
BET APR 1880 AND JUL 1880 |
* BET APR 1880 AND JUL 1880 |
The files.highlights preference points to a plain-text file that marks individuals for visual emphasis in the chart. Each line has the format:
ID [name...] [# comment]
ID— the GEDCOM individual ID (without@delimiters), e.g.I1name— optional, for documentation purposes only# comment— optional, ignored by the parser
Example highlights.txt:
I1 John Smith # root ancestor
I5 Jane Doe # married 1843
I12 Paul Smith # emigrated 1900
Highlighted individuals are visually distinguished in SVG/PDF output. They are rendered in a different text color, configurable via output.style.text.highlights.color, and with a different background color, configurable via output.style.text.highlights.background_color. The text backend supports two fallback modes, controlled by output.style.text.highlights.fallback: when set to "uppercase", the highlighted individual's name is capitalized; when set to any other value (e.g. "->"), that literal string is prepended to the left of the line (even before the ID column, if shown), and all content on that line is shifted right to make room.
When genealogical data is split across several GEDCOM files (e.g. paternal and maternal ancestries maintained separately), genechart can merge them into a single chart.
Each further GEDCOM is parsed separately and merged into the main GEDCOM's Genrep structure. An alias file identifies individuals that appear in both the main and a further GEDCOM (with different IDs in each), so they are treated as a single person. All other IDs from the further GEDCOM are disambiguated by inserting a prefix letter (B for the 2nd file, C for the 3rd, etc.) after the first character of the ID — e.g. I45 → IB45.
When an aliased individual exists in both GEDCOMs, the main GEDCOM's record wins for all non-empty fields; gaps are filled from the further GEDCOM; family links (FAMS/FAMC) and notes are always unioned.
# main_id further_id optional name (ignored)
I1 I99 John Smith
F3 F201
- First field: ID in the main GEDCOM (without
@) - Second field: ID in the further GEDCOM (without
@) #comments and blank lines are ignored- Family IDs (
F…) may also be aliased
CLI (repeatable; order matters):
genechart main.ged --merge second.ged aliases2.txt --merge third.ged aliases3.txtPaths are resolved relative to main.ged's directory.
Preferences (genechart.toml or --pref):
[files]
merge = ["second.ged", "third.ged"]
merge_aliases = ["aliases2.txt", "aliases3.txt"]merge_aliases must have at least as many entries as merge. CLI --merge takes precedence over preference-based merge pairs when both are present.
Maximum 25 further GEDCOM files (prefix letters B–Z).
# Pedigree chart combining paternal and maternal ancestry files
genechart maternal.ged -r I1 \
--merge paternal.ged mat_to_pat_aliases.txt \
--dir ancestors --type fan -o chart.svgshow.exclude prunes parts of the tree — the dual of merging — to drop uninteresting or
unreliable branches, or to split a large tree into manageable charts. List the individuals to
exclude by ID, each with an optional substitution message:
[show]
exclude = [
{ id = "I123", msg = "see separate tree for the Doe family" },
{ id = "I456", msg = "" }, # empty message: omit this individual entirely
]Tree traversal stops at each excluded individual:
- descendants — excluding a blood descendant removes its spouse(s) and all of its descendants; excluding a spouse removes that marriage's children and all of their descendants (the blood partner stays);
- ancestors — the excluded individual's ancestors are removed;
in all cases, anything also reachable through a non-excluded path is kept (reconvergence, e.g. a descendant reached via another line of a cousin marriage, or a shared ancestor in a pedigree collapse).
A non-empty msg is shown in place of the individual's name (with no birth/death/marriage
data) as a visual marker that part of the tree is missing; it is styled with
output.style.text.exclude_msg (color) and output.style.fonts.exclude_msg (font) in every
layout. An empty msg omits the individual completely (no box, row, or connector).
Endogamous families (e.g. first-cousin marriages) cause the same individual to be reachable via
more than one path through a chart. Every layout places (and, in the boxed_couples/boxes/
fancy/fan/simple sense, draws) each occurrence — the second and later occurrences of a
repeated individual are simply extra instances alongside the first.
[show.reconvergent]
duplicate = true # false: stop expanding a repeated individual's subtree beyond
# themself + spouse (descendants: no further children;
# ancestors: no further parents)
reference = "" # "" = no reference glyph; "123" = ①②③.., "abc" = ⓐⓑⓒ.., "ABC" = ⒶⒷⒸ..
# (shown on every instance of a repeated individual, even one with
# no children, to mark the reconvergence)Symbol placement is layout-specific: boxes/boxed_couples draw it near a box corner
(top-right if layout.root_pos = "bottom", bottom-right if "top"); fan draws it near a
corner of the wedge; fancy/simple draw it right after the name. Color is
output.style.text.reconvergent_reference.
When genealogical data is incomplete, show.missing_data = true replaces a blank name,
date, or location with a configurable placeholder string instead of leaving it blank (or,
for birth/death/marriage, omitting the line entirely) — useful for a draft printout that
highlights exactly what still needs to be collected (e.g. before asking relatives what they
know about a family).
[show]
missing_data = false # false: today's behavior (blank fields, omitted event lines)
[output.text.missing]
name = "name?" # whole name missing (both firstname and lastname)
firstname = "firstname?"
lastname = "lastname?"
date = "date?"
location = "location?"
[output.style.text]
missing_data = 0xE00 # color for placeholder text only — the rest of the line keeps
# its normal color- Name:
format.no_nameandoutput.text.missing.nameboth handle the case where an individual has no name at all, but for different purposes.format.no_nameis for a normal printout — a neutral placeholder (e.g. "unknown", "—", or "?") shown in the same color as any other name.output.text.missing.nameis for a draft printout that deliberately calls out the gap; whenshow.missing_data = true, it takes precedence overformat.no_namefor the whole-name-missing case.firstname/lastnamecover the case where only one of the two is missing (no equivalent existed before this preference). - Birth: shown even when there is no
BIRTtag at all — everyone was born, so a placeholder-filled birth line is always produced whenshow.missing_dataandshow.birthare both on. Marriage stays conditional on the record existing at all (never forced onto someone who never married) — within an existing record, a blankdate/locationstill gets its placeholder. - Death: shown when there is a
DEATtag (as usual), or when there is none but the individual is assumed deceased — seeshow.assume_deceasedbelow. Otherwise (noDEATtag and not assumed deceased) it is never forced, since most people in most trees are either alive or of unknown status. - Only the placeholder substring is colored with
output.style.text.missing_data; the rest of the name/date/location line (real data, or literal template text) keeps its usual color and font.
[show.assume_deceased] # only takes effect when show.missing_data = true
birth_year_max = 1925 # assume deceased if born on or before this year
marriage_year_max = 1945 # assume deceased if married (in any family) on or before this yearAn individual with no DEAT tag is assumed to have died — showing a death placeholder
instead of omitting the line — if born, or married in any of their families, on or before
these years. Either set to 0 to disable that criterion (e.g. birth_year_max = 0 to only
assume death from a marriage date, or both = 0 to disable the assumption entirely and
fall back to requiring an explicit DEAT tag).
Photos are supported in the boxes layout only. Enable with show.photo = true.
Photos are resolved relative to the GEDCOM file's directory:
-
ID-based (default):
<photos.directory>/<individual_id>.<ext>, whereextis tried asjpg,jpeg,png(both cases). For example, individualI1maps tophotos/I1.jpg. -
Index file: set
photos.indexto a path (relative to the GEDCOM directory) of a plain-text file. Each line has the formatID filename [anything...];#starts a comment; blank lines are ignored.# Kennedy family photos I0 jfk.jpg I52 jackie.jpg # married 1953 I53 caroline.jpg
scale value |
Behaviour |
|---|---|
"crop" (default) |
Fill the box exactly, cropping the centre |
"fit" |
Scale to fit within the box, preserving aspect ratio |
"none" |
No scaling; use the original image dimensions |
| Setting | Behaviour |
|---|---|
embedded = false (default) |
The SVG href is a path relative to the SVG output file |
embedded = true |
The image is base64-encoded into the SVG as a data URI |
| PDF output | Always embeds regardless of embedded |
photos.downsample caps the resolution of embedded images. The maximum pixel dimensions are computed as width * downsample / 96 × height * downsample / 96. Set to 0.0 to disable.
When box_resize = true (default), all boxes in the chart grow taller by height + 2 * margin to accommodate the photo. When box_resize = false, the photo is skipped for boxes where it would not fit alongside at least one line of text.
genechart family.ged -r I1 --type boxes \
--pref 'show.photo = true' \
--pref 'photos.directory = "photos"' \
--pref 'photos.embedded = true' \
--pref 'photos.scale = "crop"' \
-o chart.svgcargo build # debug
cargo build --release # release
cargo test # run all tests
cargo build --features lua # include the experimental Lua plugin system
cargo test --features lua # run the plugin tests toogenechart can run small Lua scripts that post-process each record as it is parsed —
for example to fold a non-standard GEDCOM tag into a name, or to normalise place strings.
This is opt-in at build time behind the lua Cargo feature (cargo build --features lua, which compiles a vendored Lua 5.4 and needs a C compiler).
See docs/lua-plugins.md for configuration, the scripting API, and
worked examples.
The boxed_couples placement algorithm is the most complex part of the codebase, and
placement bugs are hard to diagnose from the code alone. The optional bc_debug Cargo
feature emits a CSV trace of every placement decision (which node moved, by how much, in
which pass) so overlaps can be tracked to their root cause. This is a contributor/debugging
tool with zero cost in normal builds.
See docs/debugging-boxed-couples.md for the full
rationale, log format, and diagnostic workflow.
- gedinfo — a command-line tool (Python) for
querying and analyzing GEDCOM files: search individuals by ID or name, trace ancestors
and descendants, identify relationships, list roots/leaves and disconnected groups,
generate statistics, compare two files, and anonymize data. It pairs naturally with
genechart — use
gedinfoto explore a tree and find the individual IDs you need, then feed them to genechart's--root, the highlights file,[show] exclude, or--mergeto render the chart. Both read GEDCOM 5.5.1 and support custom GEDCOM tags via TOML.
MIT — see LICENSE. Third-party components (and the vendored Lua 5.4 bundled when built
with --features lua) are documented in THIRD_PARTY_NOTICES.md.
This is my first attempt at a non-trivial project that is almost completely vibe-coded. I am not proficient in Rust, which I can read but can't (yet) write fluently. I am aware that the LLM-generated code is baroque and often more complicated than necessary. However, using AI allowed me to get the job done, the alternative being no project at all, as I don't have the time to hand-code it myself.
For this project, I used mostly Claude Sonnet 4.6, with some tasks delegated to Haiku 4.5 for more efficient use of the token budget. For more complex tasks I used Opus 4.8. I also used qwen3.6:27b-coding-nvfp4 running on Ollama locally on my MacBook Pro M3 (40-core GPU with 400GB/s memory bandwidth) with 36GB of RAM. The local model ran surprisingly well, but it is orders of magnitude slower than Claude and sometimes gets stuck because of context rot. Qwen also had problems with the Edit tool, so I had to vibe-code alternative file-editing tools. In either case, I ran the models from Claude Code launched in a terminal inside Visual Studio Code.
During the course of the project I improved my prompting. My initial prompts were more terse and attempted to tackle larger problems. Over time I learned to make smaller incremental changes and to provide detailed descriptions, often supplemented with bug duplication instructions. It is an ongoing journey of learning to interact with non-human intelligence.
A.B.