Skip to content

AleBeda/genechart

Repository files navigation

genechart

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

Installation

# Install from source
cargo install --path .

# Or build a release binary in target/release/
cargo build --release

Requires a stable Rust toolchain (Rust 2024 edition).

Usage

genechart [OPTIONS] [GEDCOM_FILE]

Options

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

Examples

# 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.svg

Layout Types

simple

Text-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' --text

Configuration: [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.

boxed_couples

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.svg

Configuration: [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.

fan

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.svg

Configuration: [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).

fancy

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.svg

Configuration: [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).

boxes

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.svg

Configuration: [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.

Output Formats

Text

Plain text with column-aligned names, dates, and dot leaders. Default output format.

genechart family.ged -r I1 --text

SVG

Vector 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.svg

PDF

Generated via SVG conversion. Supports poster tiling (multi-page output for large charts).

genechart family.ged -r I1 --pdf -o chart.pdf

Poster 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.toml

where poster_style.toml contains:

[output.poster]
rows = 2
columns = 2
overlap_mm = 5.0
alignment_lines = true

Preferences File

Preferences are read from (lowest to highest priority):

  1. Installation-directory defaults (defaults.toml)
  2. User home (~/.genechart.toml)
  3. Directory TOML (genechart.toml in the same directory as the GEDCOM file)
  4. File TOML (same basename as the GEDCOM file, e.g. family.toml for family.ged)
  5. --preff <FILE> — explicit preferences file(s); may be given more than once and are applied in command-line order, so a later --preff overrides conflicting preferences from an earlier one
  6. --pref command-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.

Full TOML Example

[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.

Colors and transparency

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.

Date Formatting

Dates (birth, death, marriage) have two independent controls:

Date format pattern

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".

Date qualifiers (format.date_qualifiers)

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.

Examples

[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

Highlights File

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. I1
  • name — 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.

Multi-GEDCOM Merge

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.

How it works

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. I45IB45.

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.

Alias file format

# 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

Specifying further GEDCOMs

CLI (repeatable; order matters):

genechart main.ged --merge second.ged aliases2.txt --merge third.ged aliases3.txt

Paths 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).

Example

# 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.svg

Excluding branches

show.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).

Reconvergent trees

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.

Missing data placeholders

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_name and output.text.missing.name both handle the case where an individual has no name at all, but for different purposes. format.no_name is for a normal printout — a neutral placeholder (e.g. "unknown", "—", or "?") shown in the same color as any other name. output.text.missing.name is for a draft printout that deliberately calls out the gap; when show.missing_data = true, it takes precedence over format.no_name for the whole-name-missing case. firstname/lastname cover the case where only one of the two is missing (no equivalent existed before this preference).
  • Birth: shown even when there is no BIRT tag at all — everyone was born, so a placeholder-filled birth line is always produced when show.missing_data and show.birth are both on. Marriage stays conditional on the record existing at all (never forced onto someone who never married) — within an existing record, a blank date/location still gets its placeholder.
  • Death: shown when there is a DEAT tag (as usual), or when there is none but the individual is assumed deceased — see show.assume_deceased below. Otherwise (no DEAT tag 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 year

An 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

Photos are supported in the boxes layout only. Enable with show.photo = true.

Photo discovery

Photos are resolved relative to the GEDCOM file's directory:

  • ID-based (default): <photos.directory>/<individual_id>.<ext>, where ext is tried as jpg, jpeg, png (both cases). For example, individual I1 maps to photos/I1.jpg.

  • Index file: set photos.index to a path (relative to the GEDCOM directory) of a plain-text file. Each line has the format ID filename [anything...]; # starts a comment; blank lines are ignored.

    # Kennedy family photos
    I0  jfk.jpg
    I52 jackie.jpg      # married 1953
    I53 caroline.jpg
    

Scaling

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

Embedding vs linking

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

Downsampling

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.

Box sizing

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.

Example

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.svg

Building

cargo 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 too

Lua Plugins (experimental)

genechart 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.

Placement Debug Logging (bc_debug)

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.

Related projects

  • 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 gedinfo to explore a tree and find the individual IDs you need, then feed them to genechart's --root, the highlights file, [show] exclude, or --merge to render the chart. Both read GEDCOM 5.5.1 and support custom GEDCOM tags via TOML.

License

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.

Author's Note

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.

About

Command-line tool that turns GEDCOM genealogy files into family-tree charts — descendant trees, pedigree fans, cascading and boxed-couple layouts — as text, SVG, or PDF.

Topics

Resources

License

Contributing

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages