Skip to content

Changelog

What changed in each release of karyon, newest first, and why it changed. The Unreleased section at the top is everything since the last release.

All notable changes to this project are documented in this file. The format follows Keep a Changelog, and the project adheres to Semantic Versioning.

[Unreleased]

Changed

  • Bold and semibold text is measured at least as wide as Helvetica-Bold draws it, which is the face Arial sets a bold label in and the one a PDF sets it in. Nine characters, & ? B K L ^ i j l, were measured at Inter's narrower widths, and a bold "Bill" ran a twentieth past its room, a "?" nearly a tenth. The ten characters karyon writes beyond ASCII, the ellipsis, the en dash, ², ×, the middle dot, ω, ≤, ≥, ≈ and →, are measured as the widest of Inter, Arial and the face a PDF sets each in, with Inter's measured in a browser as the ASCII widths were. Each was given six tenths of an em, and Helvetica and Arial draw an ellipsis a whole em wide, so a label cut short could run four tenths of an em past its room. 22 of the 47 example figures and their copies on the site, and 20 of the 32 start figures, move: a track name holding one of the nine letters widens the label gutter by 0.05 to 0.51 px, which shifts what is drawn beside it and bins a coverage profile or a row of bars afresh; a legend with ω, ×, ≤ or → in it spreads by up to 4.2 px; a key after an r² sits 1.7 px closer, since a superscript two is measured at Inter's 442 thousandths rather than 600; the visual-system sheet is 0.25 px wider; and one label in the evidence tree is cut a character sooner, as embB-M30….
  • A .hic is drawn rather than refused with the hic2cool and cooler dump commands that wrote it as BEDPE. Handed to a track other than --pairs, or named for a file a track reads as text, such as the linkage --ld gives a scan, it is refused naming --pairs, as a bigWig on the wrong track is; piped in, asking for its name, since it is read out of order; and compressed with gzip, with the gunzip -k that gives it back. cooler's .cool and .mcool are still answered with the cooler dump that writes their BEDPE: they are HDF5, which h5py writes in either of two families of its structures, and reading it without a dependency is an HDF5 reader of the crate's own. Files::sequences answers for a .hic from its header.
  • FeatureRing paints a feature in its own Feature::color before the ring's colours and the strand's, the order FeatureTrack takes them in, so one annotation is painted alike drawn round or along.
  • A chord's end narrower than 0.3 degrees is drawn that wide, centred on its span. A breakend's single base was held open by a hair of 1e-4 radians, and a chord between two of them was 0.03 px across at each end, which nobody could see or point at. The tooltip still says the base, and says a one-base end once, source 40,001, where it said source 40,001 to 40,001.
  • The example annotation, docs/data/genes.gff3, writes a CDS row under each gene, as NCBI writes a gene with no introns, naming table 11, so the documentation's own files draw --codons, and examples.zip holds it. Every figure drawn from it is the same byte for byte, since a CDS from one end of a gene to the other is drawn as the gene alone was.
  • A BAM or a CRAM with no place is refused as ArgError::GenomeWideReads, saying that its depth across a whole genome is every read it holds and giving the mosdepth --by 100000 that counts it in windows, which are drawn there: karyon reads.bam was told what the first argument is. A track drawn over a place beside files that would be drawn across the genome is named as ArgError::NotGenomeWide, as --features genes.gff3 is drawn over a place, and with none a figure is drawn across the whole genome, which --coverage, --copy-number, --windows and --manhattan tracks alone are, where the line was answered with what the first argument is. A scan read with --ld or --with-recombination, or a signal with --format values, is named with the option that keeps it over a place, as --manhattan gwas.assoc is drawn over a place with --ld, since the linkage to a lead is read over one stretch of one sequence, rather than beside a list that holds its own flag. That answer now names the four tracks drawn across the genome, worked out from the tracks.
  • A --shade on a figure across the whole genome is refused, as a bare span or a gene, saying a figure across the genome rather than a scan, and one past the end of its sequence says it is past the furthest any file reaches there rather than the furthest marker.
  • An index named on its own, a .tbi, .csi, .bai, .crai, .fai or .gzi, is refused before anything is read, naming the file it indexes, as ArgError::IndexNamed: calls.vcf.gz.tbi is an index, and karyon reads it from beside the file it indexes; name calls.vcf.gz instead. It was looked for as a gene or a sequence of that name, and refused as neither.
  • A bigWig, a bigBed or a 2bit handed to a track that does not draw what it holds is refused naming the tracks that do, as BuildError::OtherTrack: --pileup signal.bw was answered with the bigWigToBedGraph command that writes bedGraph, which --pileup would have refused next. A --format given with one is refused the same way, since the file says what it holds, and one piped into standard input is refused asking for its name, since it is read out of order. A bigWig with no place after --dynseq is refused saying it needs one, as ArgError::PlacelessBigWig, where it was told what the first argument is; after --coverage and --windows it is drawn across the whole genome.
  • Files::sequences answers for a bigWig, a bigBed and a 2bit from the index each holds, as it does for a BAM from its header, so a sequence any of them names is a place drawn whole, as long as the file says.
  • read::bam::Bgzf lives in read::bgzf, and read::bam still names it. Bgzf::tell says the end of a block as the next block at offset nought, as htslib writes that place into an index, so a stretch an index says ends on a block boundary is no longer read one record past. Bgzf::fill and Bgzf::line read bytes and lines from wherever Bgzf::seek went. read::bam::Index is read::index::Index, and read::bam::index still reads a .bai alone.
  • The questions Files asks about a BAM (its depth and its reads over a window, its sequences, one read by its name) are answered from Files::seekable and Files::beside unless a type answers them itself, so Disk and Held share one reader of a BAM where each had its own, and a type wrapping another Files passes on two questions for every reader that does not read a file whole. Held holds each file once and hands it out without a copy.
  • A tree that says under it that a column of its sheet ran out of colours, drawn in shapes as well, two strips painting two values one colour, or branches of two values sharing one, says how to part them when drawn from the command line: ; --colors gives them colours of their own. Branches coloured by an annotation of the Newick are out of reach of --colors, and their line is as it was. Where a colour --colors chose made the clash, given to two values or the one the palette deals another, the line names the two values and the colour instead, as lineage: L1 and L2 are both #aa0000, so each is a shape as well, and leaves --colors out of it. docs/assets/start/tree.svg and its dark copy change.
  • --color refuses a value holding =, which is --colors with a letter lost, and names --colors; taken as a paint, country=Peru:#e7298a went into the fill as written, which is no colour at all.
  • A name ending in .png, .eps or another format is still refused by -o, and the message now names the file karyon would write instead and a tool that makes the format asked for from it: rpoB.png names a PNG file, and karyon writes SVG and PDF: write rpoB.pdf and convert it with pdftoppm, or rpoB.svg with rsvg-convert, Inkscape or a browser. An EPS is answered with pdftops -eps, an EMF with Inkscape and a .svgz with gzip, since pdftoppm writes none of them. --pdf is answered with -o figure.pdf, and --png, --jpg and --dpi with the tools that make an image from either.
  • Plot::save writes PDF when the name it is given ends in .pdf, where it wrote SVG into a file of that name, which no PDF reader opens.
  • A Windows build answers a file that is not text with the command piped into -, as pipe what samtools view -h aln.cram chr1:1-5000 writes into karyon, with - where its name is, where it offered <(samtools view -h aln.cram chr1:1-5000): cmd and PowerShell have no such thing, and Git Bash's hands over a path to a pipe that a Windows program cannot open. Linux, macOS, WSL and the playground keep the <(...).
  • The Windows release archive's karyon.exe carries its own C runtime (+crt-static), as the musl build carries its libc. Linked the default way it imported VCRUNTIME140.dll, which Windows does not ship, and the release notes said each archive needs nothing installed alongside it. The release workflow reads the binary's import table and fails if the runtime is back.
  • CI tests on Windows as well as Linux and macOS, every step of the check job, and draws the reads figure of Start here from full paths in each system's own spelling, compared byte for byte with the committed file. A file that is not an index, put beside a copy of the BAM, has to be refused, since a figure drawn without the index is the same figure. .gitattributes checks every text file out with LF line endings, so docs/data/draw.sh, which stopped at its set -e in a Windows checkout, runs there.

  • A gene from a GFF3 or GTF is drawn as a gene model: its exons over a line through its introns, with arrows along the line, and the untranslated ends of the exons at half height. It was a solid bar from end to end, which for a gene with introns says the opposite of the annotation about most of it. A gene with several transcripts is drawn once with every exon any of them uses, and its tooltip says how many it merged. A gene over its own CDS, as in a bacterial annotation, is the arrow it was, so no committed figure changes.

  • The depth of two samples on the "A whole sequence" page is drawn on one scale (--same-scale): each on its own made the second sample, sequenced deeper, look as deep as the first. docs/assets/start/genome.svg and its dark copy change.
  • A GTF of exons alone, as a table browser writes one, draws a gene once with its exons, where each exon was drawn as a gene of its own; StringTie's transcripts with no gene row are one gene of two transcripts, each with --isoforms.

  • A second place on the command line is a panel of its own, where it was refused as "one region per figure"; ArgError::ExtraRegion is gone.

  • The message for a figure with no place names every track that goes without one, worked out from the tracks: it named five, and four more had gone without one since the tables over time and over sites came in.

  • Traits::spread is Traits::strips, which says what it draws; to an R user spread read as tidyr's pivot.

  • A column's stretch of the palette, and whether it is drawn as shapes, are decided in the theme it is drawn in: a theme of more colours keeps a column of seven values a strip, where spread chose shapes against the six colours the crate ships whatever theme came after, and an explicit TraitStyle::Strip was overridden the same way.
  • Branches coloured by a column of the joined sheet take the colours a strip of it takes, drawn or not. With no strip they dealt the palette in the order the tree meets the values, so L1 was blue in one figure of a set and ochre in the next.
  • A clade folded by a value is named by it, as L4 (16 tips), where it read S26 +15 more, a sample named for a lineage, and its wedge is filled at half strength in the colour of its branches and edged in it: at 0.28 with no edge a pale wedge read as nothing. The wedges of example-phylogenetics.svg and example-phylo-layouts.svg change.
  • One ring around a circle or a cloud is named by the key rather than by a swatch across the top, which read as a key to a colour drawn nowhere; two or more are still named. example-evolutionary-surveillance.svg is 22 pixels shorter, and one panel of example-phylo-layouts.svg loses its swatch.

  • A panel of variable sites whose columns would be narrower than a pixel and a half is drawn a pixel at a time: each pixel of a row is shaded by the share of the sites under it that differ from the reference, in eight steps, and the key says so. A pixel with any difference in it takes at least the first step. A cell per site made thirty thousand sites of forty samples an SVG of 124 MB, which no viewer opens, and it is now 1.2 MB. A panel with room for its cells is drawn as it was.

  • A panel whose columns are too narrow for their position labels holds no strip for them under it: it held a hundred pixels of nothing under a panel of thirty thousand sites. A note on rows left out keeps a line of its own.
  • Molecule keeps only the sites a molecule covered. Molecule::covering builds one from those alone, call(site) reads one back, calls() walks them in order and uncover(site) leaves one out; calls is no longer a public field. Twenty thousand long reads over thirty thousand sites peaked at 707 MB and take 56, with the same figure.
  • CoverageTrack keeps its values as runs of bases holding one value, where it kept one per base. A bedGraph of windows over 200 Mb took 1.6 GB and 1.9 seconds and takes 4 MB and a few milliseconds, with the same figure. A per-base depth file costs what it did.

  • A Manhattan plot draws its hits after every miss, and every hit's ring before any hit: a tower of hits is one solid shape in the hit colour with one ring round it, where each ring cut into the hit beneath it and a dense tower read as hatched in the colour of the page. A hit at the threshold is no longer under a miss the file listed after it. example-association.svg, example-genomewide.svg, the gallery and the scan page figures change.

  • SurveillanceTrack::minimum_total breaks the line, and leaves the stack open, at a time whose every row is under the floor. The line ran straight across it, drawing a trend through the very time the floor had left out.
  • plot() puts its ruler under the last track laid on the coordinates, where it put it at the bottom: a tree, a panel of sites or a key stacked below a coverage profile sits under the ruler, which numbers what is above it, and not over it. The command line's ruler of columns, weeks, sites or samples does the same. In example-cluster.svg and example-structural.svg the ruler moves above the key.
  • The gallery is titled What karyon draws, where it claimed every representation on a sheet that leaves seven track types to other examples, and gains a twenty-third panel for PairTrack, which had none: linkage between variants as a triangle under their gene.

  • The pictures drawn in advance for Start here and the Your data pages are drawn with --background on the page's colour, where the white of the figure was replaced afterwards: the shades mixed from the ground, the pill behind the locus and the greys of the ruler, were mixed from white and are now mixed from the page. Twenty-two of the twenty-four change, only there.

  • --relative draws a heatmap either side of 1×, a loss in the theme's first colour and a gain in its second, with the usual depth pale, where one hue drew a lost stretch nearly as pale as the page. The page figures of the heatmap change.

  • A surveillance alert is a triangle in its lineage's colour, and a chip after the lineages' own says what the triangles flag, as ≥ 50% or up 15 points. Every alert was drawn in the second lineage's colour, with nothing to say what it was. example-evolutionary-surveillance.svg changes.
  • A file on disk is read again when a figure needs it twice, rather than kept: kept, every file was held twice while its track was built, and a 176 MB depth file took 435 MB to draw where it now takes 259. A pipe the shell names is kept, as it cannot be read again.
  • LegendItem::Ramp has a through field, the colour and the number a scale with a middle passes through; a match on it by its fields needs ...

  • The command line and the playground read every tree with Tree::parse, so a NEXUS file from BEAST, MrBayes or FigTree is drawn where it was refused as Newick with more than one root, and the playground keeps a tree's annotations, which it dropped. A file of several trees draws its first and says so. A file ending .nex, .nexus, .nxs or .trees named on its own is a tree.

  • An internal label in quotes is a name, as '100', where it was read as support, and one of several numbers parted by /, as IQ-TREE writes 95.3/88, is support, the last drawn and each kept as support_1, support_2 and so on, where it was read as a name.
  • Error::InvalidNewick has an at field, the character a tree breaks at, and says it: invalid Newick tree at character 1204: branch length is not a number. A match on it by its fields needs ...
  • A tree's time axis reads a date written as text, as 2020-03-15, where it drew the tree by branch length, and the warning for a tip with no time says no number or date.

  • A tree's time_unit is the time axis's title, on a line under the numbers of a phylogram and at the inner end of a circle's rings, where it was written on the latest number: 2005 year read a calendar year as a quantity of years. example-phylogenetics.svg grows a line, and the time guides of example-phylo-layouts.svg and example-phylo-map.svg move their unit.

  • A node glyph, a pie, a donut or a bubble, takes the colours no strip and no branch was dealt before the ones they were: it took the palette from its start, so a pie's first key was drawn in the first lineage's colour beside it. A tree with glyphs alone is drawn as it was.
  • collapse, reroot and CladeHighlight::new take a NodeRef, and an index or a name is one, so every call that compiled still does.
  • TraitLevel has a symbol field, the shape a level of a column drawn as shapes takes; a struct literal of one needs it.

  • The playground's controls sit under the figure rather than at the foot of its pane, where a short figure left them a pane's height below what they change. The command box wraps and grows with the command, since a control that lengthened it wrote its flag past the edge of the box. On a phone the page starts stacked, command over figure, and grows with what it holds: side by side, each pane was a strip a hundred and fifty pixels wide and the figure a few pixels tall. A place counted in weeks, sites or samples says so, where it said bases.

  • Unticking --relative in the playground took the word after it out of the command too, --with-tree among them: the page's list of flags that take no value lacked it, and --no-legend and --no-scale-bar. A test holds the list to the parser.

  • A time is written as the ruler writes it: a surveillance panel's and a phylodynamic trajectory's tooltips call time 0 time 1, as a base 0 is base 1, where they called week 12 week 11 over a ruler that said 12.

  • A phylodynamic trajectory keys what it draws, estimate, interval where there is a ribbon and log scale where the scale is one, in place of trajectory · linear. The words on its reference line sit at the left, over the line and over the points, as a scan's threshold does: at the right they sat on the latest estimate.
  • A selection track keys its colours as ω < 1, ω ≈ 1, ω > 1 and p ≤ 0.05, where it wrote omega<1 and p<=0.05 under a title reading selection atlas over every figure of one. Its axis says ω 8 and ω 1.
  • The committed figures example-selection-atlas.svg and example-evolutionary-surveillance.svg change with these, and the page figure of an alignment gains a ruler of columns.

  • Tree::reroot, reroot_outgroup, reroot_midpoint, rotate and collapse are #[must_use]: each leaves the tree as it was when it cannot do what it was asked, and its result is the one thing that says so. A call that throws the result away now gets a compiler warning.

  • A figure's description names every track it counts, each by its label or, where it has none, by what it is: with 4 tracks, drawn top to bottom: reads depth, genes, calls and a ruler. It counted the ruler and the key and named neither, so four tracks were three names long, which a simulated user reading it as a screen reader would took for a mistake. Track::noun says what a track is, and every track of the crate says it; one that does not is a track. Every committed figure's description changes.
  • The key names a column's levels as a reader looks them up, L1, L2, L4, L10, each in the colour it was dealt, where it named them in the order the sheet dealt them their colours: L4, L2, L1, which all three simulated users asked about. The colours are dealt as they were, so no strip and no branch changes colour.
  • A phylogram draws its branch-length scale bar by default, since its widths measured nothing a reader could name without one; --no-scale-bar and TreeTrack::show_scale_bar(false) leave it out, and --scale-bar is gone, answered with the new default. A cladogram, a time-scaled tree and a tree with no branch lengths draw none, and hold no room for one. Five committed figures gain a bar: example-phylo-annotations.svg, example-phylo-faces.svg, example-phylo-dnds.svg, example-evolutionary-surveillance.svg and example-selection-atlas.svg.
  • What a value axis measures is written under the track's name, -log10 p under a scan and AF under the calls, where it was words after the top tick: 10 -log10 p read as ten minus something, and nothing named the calls' axis at all. Track::axis_title says it, ManhattanTrack and VariantTrack take one, and the command line sets both. A unit written as a symbol, the x of 50x, stays on the tick. All three simulated users read the scan's axis wrong or asked what the calls' axis was.
  • The significance line says where it is: p = 5e-8 for genome_wide_threshold, p = 1e-5 for the new p_value_threshold(1e-5), and the value itself for threshold, over the line, or under it where the line runs along the top of the band. threshold_label says something else, or nothing.
  • A call's tooltip names its value as its axis does, AF 0.48, where it said value 0.48, and value still where the axis has no title.
  • A VCF call with no AF has no value, where it had 1. It still stands full height, but an axis beside it said it was a fraction of one, and a file with no AF at all drew a scale of 0 to 1 that measured nothing.
  • Eight committed figures change with these: example.svg, example-dark.svg, example-zoom.svg, example-ideogram.svg, example-pileup.svg, example-association.svg, example-genomewide.svg and gallery.svg.
  • Every column of words in a sample sheet deals its own stretch of the palette, by its place in the sheet: two columns take three colours each and three take two. They all dealt it from the first colour, so a lineage and a country were one colour side by side, L4 and Kenya the same blue, and two simulated users called it misleading. The stretch goes by the sheet rather than by what is drawn or how many levels come first, so neither --columns nor an appended sample repaints anything. A tree coloured by a column starts where the column does. TraitColumn::first_color sets it by hand. example-snps.svg changes: its resistance strip no longer shares the lineage strip's colours.
  • The command line names the bases' colours in the key under the figure while they are blocks too narrow for their letters, in the reference and in a pileup's mismatches, and says nothing of them once each is lettered. No simulated user could say which colour was which base. Track::key is what a track needs explained at a zoom, Figure::key gathers every track's at the zoom the figure draws at, BaseColors::legend is the key to the four colours, and Legend::and joins two keys without saying a colour twice. No committed figure changes: the library adds no key a caller did not ask for.

  • karyon help <track> gives what that track does with an option several tracks share, where it gave every track's: the tree's help ran to seventy lines, through what a scan does with --threshold and a pileup with --max-rows, and is now fifty-seven. karyon help all keeps every word.

  • karyon --help fits on one screen: three examples, the grammar, every track grouped by what it draws, the figure options, and where to ask for more. It printed the whole of the help, some two hundred lines, which karyon help all still does, and karyon with nothing after it prints the short help rather than an error.

  • The site's figures are part of the page rather than pictures laid on it: each is drawn on exactly the colour behind it, the page or the card it sits in, with no plate, frame or shadow, in the page's light or dark. None keeps a theme of its own any more: the visual-system sheet takes the one it is given like every other figure, and the pages that showed the dark example beside the light one show one figure and say to switch the page to see the other. While the program arrives a figure keeps its room and shows nothing rather than the file, which is light on white, and the file shows only if the program never does. The home page's and the playground's figures, which the command line draws, leave their page transparent for the same reason.

  • An annotation with no value to show is marked n/a, where it was an em-dash: in a trait strip, a ring and a tooltip, and wherever an annotation that is not a number is written out. A dash in a column of numbers reads as a minus sign or a range, and NA is a value a column of places can hold, North America or Namibia. example-phylogenetics.svg and example-phylo-layouts.svg change in those three cells, and no committed file holds an em-dash any more.
  • Every figure is drawn in a new visual system, the one karyon's mark and its site are drawn in. The palettes are indigo, pink, teal, ochre, plum and orange, measured so that the closest pair stays 10.7 apart in OKLab hundredths on a white page and 14.4 on the dark one under simulated protanopia, deuteranopia and tritanopia; the Okabe-Ito set they replace came to 6.7, and the previous dark set to 1.6, two of its colours one colour to a reader with deuteranopia. A test holds both to their numbers. The two themes hand out the same hues in the same order, so a figure keeps the names of its colours when it changes page, and the dark page is the deep indigo the site draws on. Text is set in Inter with coordinates in JetBrains Mono, both falling back to fonts every system has, and measured as the wider of Inter and Helvetica, so a label never overruns its room in either. Track names are semibold in the quieter ink and keep the case they were given; the locus sits in a pill beside the title; a title too long for its line is set smaller before it is cut short; the ruler is drawn in the quiet inks; the hairline between tracks is gone; and a coverage area fades from its line to the baseline rather than wearing a flat wash. The dark ink contrast_ink hands out is the new foreground, #1a1233. Every committed figure changes.
  • A structural variant's arc is measured against the widest call in view, not the widest in the track. One translocation reaching 1.2 Mb away used to set the scale for everything, and a 10 kb deletion in view arched to a tenth of the band. The widest call lying wholly inside the view now reaches the top, anything wider reaches it too, and when every call in view crosses an edge each counts as no wider than the view. The top keeps room for the heaviest stroke and, when one would be drawn there, the name over the tallest arc; a band too short for that name drops it rather than cutting it. StructuralTrack::widest_in and arch_in say what a given region draws; widest and arch answer for a view that holds every call, as a fraction of the band less that room.
  • Value axes tick at round values. Every track with a quantitative axis used to label the ends of whatever range the data came to, so a depth peaking at 71.46 read 35.73 and 71.46, a population size read 2167.255 and a dated tree read 2022.965. Ticks now fall on multiples of 1, 2, 2.5 or 5 times a power of ten, a free end is rounded out to one of them, the unit is written once on the highest tick, and a band too short for every label keeps a regular subset of them, so labels no longer overlap or lose their first characters to the clip. A pinned range is still taken literally.
  • A gene, a strand-coloured read and a lettered alignment cell take a wash of their colour with the full colour on the edge, as the genes of a locus already did, so the small marks over them keep the attention. Track names are set in the foreground ink with a hairline between named tracks, the title is 16 px rather than 18, the name in the middle of a circular plot keeps a halo of the page when a chord crosses it, and a category too long for its strip is set a size smaller before it is cut short.

  • read::signal::dense is read::signal::spans and hands back half-open (start, end, value) spans rather than one entry per base. A bedGraph row is one row however many bases it covers, and expanding it on the way in made the cost the width of the window rather than the size of the file: a forty byte row across a chromosome asked for 5.5 GB, and fifty tiled rows, a kilobyte of input, asked for 6.0 GB. Both now take 2.15 GB, which is CoverageTrack's own buffer of one value per base and the honest cost of what it holds. Every committed figure is byte for byte what it was.

  • CoverageTrack::from_spans lays those down, and from_pairs calls it, so scattered points still arrive the way they did. CoverageTrack::at reads one value back out.
  • read::signal::windows and read::point were checked for the same pattern and do not have it, measured rather than read: the same forty byte file takes 2.1 MB through --windows, and two hundred thousand calls take 125 MB through --variants. Both already keep one entry per row.

Added

  • read::hic reads Juicer's .hic a window at a time, with no dependency: header_of for its sequences, their lengths and its resolutions, contacts for the cells of the map of a window's sequence with itself at one resolution, as Pairs of two bins and a raw count, resolution_for for the finest that cuts a window into BINS bins or fewer, and bedpe for the text hictk dump --join prints. Only the header, the master index, one resolution's list of blocks and the blocks that can hold a cell of the window are read, each block in either of its layouts, rows or a dense square, with its bins and counts written short or long. Every window hictk dumps of the fixture it is tested on is read cell for cell, and 380 random windows of three files hictk wrote, 2.96 million cells, matched. Version 9 is read, the one hictk writes, and older files are refused by their version with the hictk convert commands, through a .mcool, that write the same map as version 9; normalised counts are not read.
  • --pairs draws a .hic, and a .hic named on its own is drawn as one: karyon chr1:20,000,001-22,000,000 contacts.hic. --resolution <BASES>, after --pairs, names the resolution, and is refused naming the ones the file holds where it does not hold that one, and after a file that is not a .hic, as BuildError::Unresolved. Without it the finest that keeps the window to 250 bins is drawn, and a note names it where the file holds finer. A contact map is always a triangle, unless --style arcs says otherwise. Over a chromosome of 249 Mb simulated in a .hic of 49 MB, a window of 2 Mb reads 0.5 MB of it and draws in 0.02 s and 7 MB, and the whole chromosome at 1 Mb in 0.04 s. A sequence the file names is a place drawn whole, as long as its header says. TrackSpec gains the public field resolution, so a TrackSpec written out field by field needs it.
  • --circular draws the place, one whole sequence, as a circle, the Rings plot only Rust reached: each track a ring in the order written, the first outermost, inside a ruler, as karyon NC_000962.3 --circular genes.gff3 calls.vcf.gz sampleA.bedgraph. Annotation is a FeatureRing painted a strand a colour, named where it holds at most 20 named features; a depth is a SignalRing cut into a thousand arcs with its --aggregate and read either side of its median, so a loss dips inside the line and a gain stands outside it; windows are the mean of each arc, read either side of 0; calls are a MarkerRing coloured by consequence; structural calls are their footprints, with a chord across the middle for each breakend join on the sequence, between the two bases a band of the same file names; and a FASTA is its GC skew. The key under the circle names each ring, outside in, with what its colours mean, and each ring's name is its tooltip. --same-scale puts every ring of depth on one reach. The circle is as long as a FASTA, a BAM, a VCF's ##contig or a GFF3's ##sequence-region says, or a span written from base 1, checked against every length the files' headers and indexes state and read no further; a sequence no file gives the length of is refused rather than closed where its rows stop, and so are one that two files give different lengths, naming each file and its length, a gene, a span from anywhere else, several places, a track with no ring, named, and an option a ring would leave unsaid, --log, --max, --style and --shade among them. The playground draws circles as a shell does and has an example of one, and the page on a whole sequence draws one from the documentation's own files, in circle.svg.
  • Ring::label, with label on AxisRing, FeatureRing, SignalRing and MarkerRing: a named ring is one group under its name, so a pointer anywhere on it says which ring it is, and the plot's alt text names its rings outside in. Rings::key(name, legend) writes a line of a key under the circle and grows the image to hold it. SignalRing::from_spans cuts a per-base signal into equal arcs with an Aggregate, samtools depth over a chromosome being four million lines that were four million sectors, and SignalRing::baseline_at_median reads it against its usual level. A plot that uses none of them draws byte for byte as it did.
  • --codons draws the codon ruler, CodonTrack, from the command line, which reached it from Rust only. It reads no file of its own: placed on a gene by its name, as karyon rpoB genes.gff3 ref.fa --codons, it numbers that gene's CDS as the figure's GFF3, GTF or BED writes it, and over a place written, the CDS of the one gene that codes there, which a page that moves the window keeps counting, read through the annotation's .tbi or .csi where it has one, as its features track reads it, and not whole, which cost a window of a few hundred bases every row of the genome's annotation in time and memory. Codon 1 is the start codon, at the right on the reverse strand, the letters are the figure's --sequence translated, and a trailing partial codon is left off with a note. The table is the one the CDS row names with transl_table=, as NCBI writes one, or --genetic-code N, which wins and says so where the two differ, and is refused for a number NCBI does not list. CodonTrack counts one unbroken span in one frame from its start codon, so a CDS split by introns is refused as CodonRefusal::Spliced rather than numbered across them, CDS rows that overlap or meet out of frame, as NCBI writes a ribosomal slippage, as CodonRefusal::Frameshift, naming the base where the frame changes, and a CDS that does not begin on its start codon, whose 5'-most row has a phase of 1 or 2 or says start_range (end_range on the reverse strand), as NCBI writes a CDS at the edge of a contig, as CodonRefusal::Partial. A figure with no annotation, a sequence drawn whole, a gene with no CDS row, two genes in the place written, transcripts that code different stretches, a gene on no strand and a CDS naming a retired table are refused with what to write instead, the genes or transcripts named five at most and the rest counted. A ruler over the wrong stretch names the wrong residue at every codon and looks no different, so each is refused rather than drawn. A figure with no place is refused as ArgError::CodonsWithoutPlace before anything is read. --codons takes --label, the gene's name by default, and --color; it is on the scales line of karyon --help, karyon help codons lists its options, and the playground has an example of a gene whose CDS names table 4, where TGA is tryptophan, its thirty-third: its page said thirty-one where it held thirty-two. Thirty-five of the thirty-eight track types now have a flag.
  • ncbi_table(id) gives every translation table NCBI lists, 1 to 6, 9 to 16 and 21 to 33, as the sixty-four residues CodonTrack::genetic_code takes, and None for a number NCBI retired; a test holds each to the codons NCBI's notes say it reassigns. read::interval::coding reads each transcript as it codes, as a Cds: its exons and its CDS as written, a CDS from one end of a gene to the other included, which read::interval::transcripts folds into one piece for drawing, and beside it the CDS rows apart, each a CdsRow with its phase and whether NCBI marks it as going on past its 5' end, since joined, two rows that share a base are one stretch. A CDS row under no gene is a coding sequence of its own, with every other row under its ID=. read::interval::translation_table reads the transl_table= of the CDS rows over a span.
  • A figure with no place is drawn across the whole genome from coverage, windows and copy number as well as from a scan: karyon tumour.bedgraph tumour.cns --ploidy 2 lays every sequence a bedGraph, a samtools depth file, a bigWig, a --windows file or a segment table names end to end, in the order chromosomes are counted, each as long as the furthest any file reaches on it, with the alternating named bar of a genome-wide plot under them, and a scan beside them shares the layout. It was refused for having no region. A sequence a coverage file names no row on, where another file names it, is drawn as missing, a gap in its line, rather than as a depth of nought, which said every chromosome a sample's file left out was lost; a gap between rows on a sequence it does name stays nought. A bigWig is read through its zoom levels, each sequence as long as its index says, from the level of which a pixel of the whole genome holds two bins, so a bigWig with no place is drawn there where it was refused as ArgError::PlacelessBigWig, which now answers only --dynseq, whose scores are drawn as bases. A .bed named on its own is told once read: mosdepth's .regions.bed.gz is drawn across the genome as the bedGraph it is, and one of features is refused for want of a place as it always was. --rename makes two files' names for a sequence one, and names it so under the figure, and a --shade finds it by either. Files that name one sequence draw it whole, as though it had been written as the place, ruler and all, which a page can move along; so does a scan of one chromosome, which was a genome of one under a bar of one name. Painted a sequence at a time, a bedGraph of 2.9 million rows over 22 chromosomes in the order sort -k1,1 leaves them draws in 0.3 s, and with each sequence looked up by its name, an assembly of 100,000 scaffolds draws its windows in 0.07 s and its segments in 0.2 s. Sequences whose lengths sum past what a u64 holds are laid to the end of the axis, as a depth over them is, where a scan, windows and segments panicked. read::signal::genome_spans, read::signal::genome_windows, read::segments::genome_copy_numbers and read::bigwig::genome read every sequence of a file, and Invocation::genome_wide, through TrackSpec::genome_wide and Kind::genome_wide, says when a command line is drawn so. The Your data page of a whole sequence draws twelve chromosomes' depth and copy number from tumour.bedgraph and tumour.cns, which examples.zip holds too, in docs/assets/start/genome-copies.svg and its dark copy.
  • CopyNumberTrack::across(&genome) lays a segmentation on a Genome of several sequences end to end: the pixel column a join falls in draws a riser for each sequence and none from the level one ends on to the level the next begins at, which drew a breakpoint nobody reported, and a segment's tooltip says where it is on its own sequence, as 5:12,000,001 to 13,500,000, rather than on the shared axis.
  • .cns and .seg named on their own are copy number, CNVkit's segments and the table IGV and GISTIC2 read, and still need --ploidy.
  • read::bcf reads a BCF a window at a time, through the CSI bcftools index writes beside it, from anything that reads and seeks, and hands its records over as the VCF text bcftools view prints for them, byte for byte, floats rounded as htslib rounds them, a GT under VCF 4.4 prefixed as htslib prefixes it, and one a sample left out, as 4 under DP:GT, printed . before VCF 4.4 as bcftools 1.24 prints it: window the records over a window, through an index or by reading every record, whole every record, counted the records on each sequence, from the counts a CSI keeps or by reading every record, and header_of the header, with its sequences and its samples. Fields says how much of each record to write: its site alone, as bcftools view -G prints it, which leaves its samples' bytes undecoded; its site and each sample's GT; or every field. The command line reads a BCF as it is, named on its own or after --variants, --genotypes or --structural, and draws from it the figure the same calls draw as VCF: --variants and --structural read sites alone and --genotypes GT alone, --structural every record, as it reads a VCF, and a cohort's BCF named on its own says how many samples --genotypes draws from the names its header gives. It was refused with the bcftools view that writes it as text. Over a chromosome of 248,956,422 bases, a million records of 200 samples, a BCF of 69 MB draws a window of 2,000 bases through its .csi in 10 ms and 6 MB, as the VCF of the same calls does through its .tbi, and without an index in 2.9 s and 4 MB, where the VCF's 841 MB of text took 5.9 s and 851 MB read whole. A .csi older than its file, one that does not read as an index and one whose first record is not where the file's header ends is read past, with a note and the bcftools index -f that writes it again; an empty window says what the file holds from the counts the .csi keeps, a sequence the header names and no record is on counted as none, and from the file's own records where the .csi is one the window read past, whose counts would be another file's; a sequence its header names is a place, as long as the header says, under the name --rename gives it, or as far as its records reach; and a record a track refuses is named by its place, since a BCF has no lines. Compressed as bcftools view -Ob writes it, uncompressed as -Ou does, or bare, BCF 2.2 is read, as htslib reads it; Binary::of names a bare one by its magic, and one piped into standard input is refused asking for its name, or for the text bcftools view writes. Each is held to bcftools view 1.24 on small files it wrote, kept in src/read/fixtures/bcf with the script that writes them again.
  • A file compressed with bgzip and indexed by tabix or bcftools index is read a window at a time through the .csi or .tbi beside it, looked for as htslib looks, a .csi first: its header, which is where a VCF names its samples and a table its columns, and the rows over the window, for --variants, --genotypes, --coverage, --windows, --dynseq, --junctions, --heatmap, --manhattan over a place, --features of a BED or of a GFF3 that says it is one, and --methylation given --modification. The figure is the one the whole file draws, byte for byte, wherever the whole file draws one: rows outside the window are not read, so a row the whole file is refused for, as a VCF line of seven columns, refuses only a window it lies over. On a VCF of 200 samples and 983,840 rows, 825 MB of text in 82 MB of bgzip, a window of 2,000 bases took 4.35 s and 869 MB and takes 5 ms and 4 MB; --genotypes over it took 2.84 s and takes 8 ms; a megabase took 4.49 s and 882 MB and takes 0.22 s and 70 MB. A figure placed by a gene in a GFF3 beside those calls took 8.85 s and 1.30 GB, the calls read whole once to look the gene up and again to draw it, and takes 7 ms and 4.5 MB: while a place is looked for, such a file is read for its header alone, and for how far its rows reach only where no file says how long the sequence is and no gene is called that. A GFF3 is read over the window and then over as far as the genes over it reach, so a gene whose intron covers the window keeps every exon. Structural calls, a recombination map, a GTF, a GFF3 whose exons name a transcript it has no row for, which the rows it opens with or the rows over the window show, a bedMethyl with no code named and a long table of windows are read whole, each for a reason docs/guide/cli.md gives. The guide turns PLINK 1's table, padded with spaces, into the tabs tabix splits a row on.
  • An empty window of a file read through its index says what the file holds from the counts the index keeps, without reading the file whole, and a window a reader refuses is read again whole: a row is refused on its line in the file, and a scan with no header whose values over the window all look like p-values is drawn as its whole file says.
  • An index that does not fit its file is read past, and the file read whole with a note saying why and with the tabix command that writes the index again: one older than its file by the second, which htslib warns of and reads through, and which a copy that kept no times makes too, cp without -p, rsync without -t or an archive unpacked without its times; one that does not read as an index; one written for another file, by where its rows begin and where it puts them; and one beside a file compressed with gzip rather than bgzip. The note is written where the file would have been read through a sound index, and not beside a GTF or any other file read whole whatever its index, which an index written again would leave as it was. An index whose counts add up to more than a count holds, which only a damaged one says, is read as one that counts none. Files held by Held or a playground page, the index beside its file by name, are read the same way.
  • read::tabix reads the rows of a bgzipped text file over a window through its index: head is the header and the first row, checked against where the index says the rows begin, rows the rows over a window, stopping at the first past its end, and window the two together, which every reader of text takes as it takes the whole file. read::interval::reach is how far the gene models over a window reach either side of it, and read::interval::parentless whether rows hold a model with no row of its own, exons naming a transcript the file never writes, which no reach holds.
  • read::bigwig, read::bigbed and read::twobit read UCSC's bigWig, bigBed and 2bit a window at a time, through the index each one holds, from anything that reads and seeks, and the command line reads all three as they are, named on their own or after a flag: a bigWig by --coverage, --windows and --dynseq, a bigBed by --features, and a 2bit by --sequence, --orfs, --with-sequence and a pileup reading against the figure's reference. Over a window many bases to a pixel, --coverage reads a bigWig from the coarsest zoom level of which a pixel holds two bins, each bin painted with what --aggregate takes of a pixel and the bases no value covers counted as nought, and the scale running to the most of the values under the window, read::bigwig::Signal::most, whichever of the three paints the bins: scaled to the bins, values up to 25.5 were drawn on a scale up to 5 by their means, and on none by their least. A chromosome of 248,956,422 bases in 4,684,581 spans, a 38 MB bigWig, draws 900 pixels wide in 0.01 s and 4 MB, where its bedGraph took 0.95 s and 216 MB; 154 of the 792 columns come out higher, from a bin straddling two columns, and none lower. A window of 10 kb draws byte for byte what the bedGraph draws. A bigWig written without zoom levels is read as written, and a value it holds as not a number, or as infinite, is read as one and drawn as the bedGraph holding it draws it, as a gap. kent indexes only the sequences that hold data, so over several places, one on a sequence a bigWig or a bigBed does not name says it has nothing there, as BuildError::Absent, where the whole figure was refused; drawn alone, that place is refused naming the sequences the file has. Compressed with gzip, any of the three is refused with the gunzip -k that gives it back, where it was told that karyon reads text and given the UCSC command that writes its text, which refuses a compressed file too. A bigBed keeps the columns its header says are BED's own, so a BED12 keeps its exons and a narrowPeak's signal value is not read as a coding start, and a 2bit gives its runs of N and its soft-masked runs as twoBitToFa writes them. Each reader is held to the UCSC tool that undoes its file, bigWigToBedGraph, bigBedToBed and twoBitToFa, on small files kent's own tools wrote, kept in src/read/fixtures with the commands.
  • A gene a bigBed names is a place a figure is drawn over, and a stretch a --shade names, found by reading its rows; a bigBed of more than 64 MB is not read whole for a name, and the figure says so. Each row's sequence is looked up by its number, so one of 20.9 MB naming 200,000 sequences places a figure in 0.9 s, where looking it up among them all took 16 s.
  • read::gzip::zlib, a zlib stream with its Adler-32 checked and a bound on what it may inflate to, which is how a bigWig and a bigBed compress their blocks.
  • read::index reads the three indexes htslib writes, BAI, TBI and CSI, into one Index: the tree of bins at the depth the file says, which for a CSI was 0 from samtools, 6 from bcftools and 8 from tabix -C, and Index::chunks, the stretches of the file that hold the records over a window. A TBI, and a CSI written for text, also give the columns and the names of the sequences tabix was told, and every index the rows it counts for each sequence, and Index::binned whether it has bins for a sequence at all, which htslib writes none of for one with no records. Over a grid of 2,880 windows the rows the chunks hold are the rows a scan of the file finds, and over twenty-three windows they are the rows tabix 1.24 prints.
  • read::bgzf::is_bgzf tells a file bgzip wrote, which an index can point into, from plain gzip, which has to be read from its start.
  • read::bam::window reads a BAM through a CSI as through a BAI, and the command line looks for one before a .bai, as samtools does: reads.bam.csi, reads.csi, reads.bam.bai, then reads.bai. A BAM with both is read through the one samtools reads, and a .csi that is not one is refused, as samtools refuses it.
  • Files::seekable, a file's bytes to read out of order, and Files::beside, the file beside it under an ending, as an index is, with its name and whether it was written before the file. Disk and Held answer both, and the playground's page passes them on.
  • --genotypes FILE draws the call of each sample at each site of a cohort's VCF, a row per sample and each call at its position, so a column of calls stands under the lollipop of its record and the gene it falls in. A cell is the share of the call's copies that are not the reference, which reads the same for a haploid, a diploid and a polyploid call: a reference call is a short bar, a heterozygous call half the hue, an alternate call all of it, and no call a pale cell. Sites closer than a cell are pooled a pixel at a time, and any alternate copy under a pixel is at least the first of eight steps, so twenty thousand sites of two hundred samples are 0.94 MB, where a cell for each, as --matrix draws them, was 258 MB. It takes --with-tree, --traits, --columns, --row-height, --max-rows (40 by default) and --no-names, and its key names the states it drew.
  • GenotypeTrack, GenotypeSite, Genotype and GenotypeState, and Plot::add_genotypes, behind it. read::point::genotypes reads GT from wherever it is among a VCF's keys, a call with any copy unknown is no call, and read::point::samples reads the names on the #CHROM line without reading a row.
  • --sample after --genotypes takes a list, the samples to draw and the order to draw them in, as --sample S07,S01,S12; a name the VCF has not got is refused with the first five it has.
  • A VCF of several samples named on its own is still drawn as its calls, and says that --genotypes draws its samples, a row each, unless the figure draws them already. --vcf and --samples, as other tools spell them, are answered with --genotypes and --sample too.
  • cohort.vcf.gz, forty samples called across rpoB, in the example files and in examples.zip, and a "Genotypes of many samples" page drawn from it, docs/assets/start/genotypes.svg and its dark copy. The playground has an example of its own.
  • --colors COLUMN=VALUE:#rrggbb,... gives the values of a --traits column colours of your own, the ones a field already knows its lineages or countries by: in its strips, in the key and along the branches --color-by paints. Values are joined by commas, and the flag is written again for more of them or for another column. A pair ends at its colour, so a value may hold a colon, or a comma with no colon before it, as in country=Korea, Rep.:#aa0000. It is a figure option, so every sheet of the figure that has the column paints its values alike, and a column of seven countries stays a strip rather than a column of shapes. A colour that would paint nothing is refused: with no --traits on the line, for a column no sheet has, for a column of numbers, for a value no row holds, for a column no track draws, and for a value given two colours. It takes the written form only; a colour file, as Nextstrain's colors.tsv, would be the one figure option that reads a file.
  • --palette, --colormap and the like are answered with --colors, and --tss, --operons and --terminators with --features: a BED12 whose thick span is the part that codes, or a GFF3 or GTF with UTR or CDS rows, already draws each RNA with its 5' leader at half height. No common format says how an RNA stops, so TranscriptionUnitTrack stays library only, and the annotation reference says what does reach the command line.
  • PDF: -o figure.pdf writes the figure as a one-page PDF, and every drawing, Figure, Panels, Rings, Map and PhyloMap, has to_pdf and save_pdf beside to_svg and save_svg, with Plot::to_pdf for a plot. The PDF is the SVG read back by Pdf::from_svg, not a second way of drawing, so a track written outside the crate gets one for nothing. A pixel is three quarters of a point, the size Inkscape and rsvg-convert give the SVG; text is set in Helvetica, Courier and Symbol with no font embedded, and stays text; a coverage fade is an image with its opacity in a mask, which Ghostscript, poppler, Quartz, Chrome and Inkscape all draw in full; and the figure's title and description are the document's title, subject and alt text. Pdf::notes names anything the page could not carry, as a character no base font has, and the command line prints it. Pdf::from_svg reads a length in pixels or any absolute unit, so an SVG sized in millimetres comes out at its size with its drawing scaled to fit, and names a percentage or an em in the notes rather than dropping it. The reference has a page on how the PDF is made.
  • Figure::shade and Plot::shade shade a stretch of the axis down every band laid on it, a Shade each, as a genome browser marks a region of interest; Shade::name writes its name at the head of the column and Shade::color gives it a colour of its own. The figure draws it, since a track is clipped to its band: the wash behind every track, so no data colour changes, and dashed edges over them, so a heatmap that hides the wash still shows where the stretch is. A tree, an ideogram or the key breaks the column, and Track::shows_shades is how a track says no, which synteny does for its lower bar. A named shade in view adds one row of 19.5 pixels at the plain scale; a figure with none in view writes the bytes it wrote before, and no committed figure changes. The alt text names every stretch shaded.
  • --shade PLACE[=NAME] on the command line, once for each stretch, anywhere on the line: a span, one base, a gene the annotation names, shaded over its own ends rather than with a margin, or a span with no sequence, as 120-180, on an alignment's columns or a table's weeks. A gene of --loci is shaded in whichever genome's row draws it, and every gene is looked up in one read of each annotation for the whole figure, however many panels it has. A time is said as its ruler says it, 2012 to 2013, and never in the thousandths a table with fractions is drawn at. Each panel of a sheet shades what is on its own sequence and a scan across the genome through its offsets. A stretch on a sequence no place is on is refused, and one outside the window is a note, so a page moving the figure keeps drawing. It reads no file: --shade genes.bed points at --features. --highlight with a place, and --roi, --vhighlight and --axvspan as other tools spell it, are answered with --shade.
  • MatrixTrack::max pins the value a colour ramp ends at: the top of a sequential one, whichever of it and CellScale::Sequential's max comes last, or the end of the gain of a diverging one, whose loss keeps its own reach. --max takes it after --matrix and --heatmap, where the key of --heatmap depths.tsv --relative --max 3 ends at 3× rather than at the 2.19× the table reached, and a top at or under the centre is refused. After --pairs it is the ceiling, and wins over the r² of 1 linkage is read against, and after --windows it is the top with the bottom as far below the line: windows reaching 1.4 read -1.5 to 1.5, and -2 to 2 with --max 2.
  • node tests/karyon-wasm.test.js checks how the pages rewrite a command, in CI beside the tree viewer's scripts.
  • Figure::same_scale and Plot::same_scale draw the tracks that measure the same thing on one scale: coverage in the same units on the same kind of scale, windows about the same baseline, scans of the same statistic. Each profile was scaled to its own largest value, so two samples side by side were two scales the eye reads as one. A track whose maximum was pinned keeps it. Track::extent, Track::y_axis_width_over and DrawContext::extent are how a track takes part, and Figure::extents with Figure::share_extents share a scale across the panels of a sheet.
  • --same-scale on the command line does the same, across every panel of a figure of several places, and --max pins the top of a coverage, a recombination rate or a scan, for figures drawn apart. --ymax and --autoscale, as other tools spell them, are answered with these two.
  • Feature::exons, Feature::coding, Feature::transcripts and Feature::gene, and FeatureTrack draws a feature that carries them as a gene model. Exons closer than a pixel are drawn as one, and a model too small on screen to show an intron is drawn as the plain arrow.
  • --isoforms after an annotation draws each transcript on a row of its own, named as the file names it, with its gene in the tooltip; read::interval::transcripts is the reader behind it.
  • A BED12 row is read with its blocks as exons and its thick span as what codes, where they lie inside the row: a narrowPeak spends the same columns on other numbers, and those are not read.
  • example-gene-models.svg, an illustrative locus of RNA-seq depth, splice junctions, two genes drawn once and their four transcripts, in the annotation reference.
  • A scan with no place is drawn across the whole genome: karyon trait.assoc lays every sequence the --manhattan tables name end to end, in the order chromosomes are counted (the numbered ones, X, Y, the mitochondrion, then the rest), each as long as its furthest marker, in the alternating shades a genome-wide plot is read by and named under the scan. It was refused for having no region. read::point::genome_associations reads every row of a table in one pass, and Invocation::genome_wide says when a command line is one.
  • Several places draw one panel each: karyon rpoB katG inhA reads.bam genes.gff3 draws the same tracks over each, one under the other with their plotting areas aligned, the title over the whole figure and the key once under it. A track with nothing in one place says so there, as no variants here, rather than refusing the figure; a figure of that place alone is still refused. Invocation::more and Place carry the places after the first, stack::build_sheet builds the sheet, and the site and the playground draw such a command whole.
  • The Your data scan page draws every chromosome of a twelve-chromosome example, trait.assoc, which examples.zip holds too, and the reads page says how to draw two genes at once.
  • plot_alignment(rows) plots a multiple sequence alignment over its columns, as many as its longest row, with the ruler counting them and no locus to work out and hide.
  • TraitColumn::colors and Traits::colors give levels colours of their own, which the strip, its key and the branches coloured by the same values all take.
  • A phylogeny says under the tree when a strip of more values than colours is drawn as shapes, and when two strips paint two values one colour, and the command line prints it. The tree of the Your data pages says it of its seven countries, and is a line taller.
  • The figures of Start here and of the Your data pages are drawn by the program in the page, from the command printed above each one and the example files the site publishes: in the page's light or dark, at the width of its column, with what each mark is under the pointer, and along the genome under the reader's hand where the figure is over one. On a phone a figure is drawn at the phone's width, where it was a 720 pixel picture shrunk to half. The pictures drawn in advance stay for a page without JavaScript, and a test holds each one to its command drawn in the page.
  • --background HEX draws a figure on the colour of the page or the slide it goes on, and the shades mixed from the ground follow it.
  • cli::stack::Held holds a command line's files in memory by name and reads them as Disk reads them from a path, a BAM through its index and gzipped text out of its wrapper; cli::stack::build_figure builds a command line's figure in a given theme and over another window, and says whether it runs along a genome (Built); Invocation::files names the files a command line reads, and TrackSpec::sources those of one track. The playground's program reads a page's files as bytes through them, and its command, command_region and command_files draw a page's command.
  • --with-recombination FILE after --manhattan lays a genetic map's rate behind the scan's points, read off a scale of its own on the right with cM/Mb after its highest number, as LocusZoom draws one, and keys it. ManhattanTrack::recombination does it in Rust, and Track::right_axis_width lets any track ask for a strip right of its band, which the figure takes from every plotting area as it does on the left. The LocusZoom page figure lays the map over the scan where it drew it as a track under it.
  • The lead variant of a scan coloured by linkage is called by its name, from the scan's SNP or ID column or from PLINK's .ld, over its diamond, in its tooltip and in the key, where its position was written (ManhattanTrack::lead_name, Associations::names, read::pairs::names).
  • --with-moves FILE after --squiggle puts the bases the basecaller called over the current, from the move table Dorado writes with --emit-moves, in SAM or in BAM, which is read from end to end as a basecaller writes one unsorted. A read Dorado split out of a longer one is placed by pi:Z and sp:i (read::series::moves, read::bam::named, Files::named_read). The page data gains moves.sam.
  • --frequencies takes --threshold as the frequency a lineage is flagged at, --growth for a rise in points of frequency from one time to the next, --min-total for the fewest samples a time needs, and --counts to draw counts of samples rather than frequencies.
  • CellScale::Diverging draws a matrix either side of a centre, each side at full strength at its own furthest value unless a spread is given, and --center sets the centre of a heatmap, as 0 for a log ratio. Legend::diverging keys it, with the centre written between two halves.
  • A table of windows in the long form, a sequence, a start, an end, a sample and its value to a row, is read by --heatmap as the wide one is (read::table::long_windows).
  • A table of its own place, an alignment, a table over time or over the sites of a gene, or a signal, is drawn from standard input without a region, and a table with fractional times from standard input is read as a continuous time: the figure keeps what the pipe gave, whatever Files it is drawn through. Both were refused.
  • A contact map as cooler or Juicer writes it, a .cool, .mcool or .hic, is recognised and answered with how to write it as the BEDPE --pairs reads: the cooler dump command for a .cool, and the steps that pick a resolution first for the other two.

  • Tree::parse reads Newick or NEXUS, whichever the text is, with its annotations, and Tree::parse_all and Tree::count_trees read and count a file of several.

  • Tree::support_from and TreeTrack::support_from, and --support-from on the command line, read a clade's support from an annotation, as BEAST keeps it in posterior and MrBayes in prob; the command line refuses a key no clade carries, with the ones they do.
  • read::date::decimal_year reads a calendar date as a decimal year, Tree::time_value is the time a node is placed at, and Tree::date_from_height turns BEAST heights into calendar dates from the most recent tip's date.
  • Two tips of one name, and branches of a negative length, are said under the tree.

  • NodeRef names a clade by index, by name, as the smallest clade holding some tips (NodeRef::mrca), or as the clade of the tips carrying a value (NodeRef::holding("lineage", "L4")). A clade found that holds tips it was not named for is used, and the tips are said under the tree.

  • TreeTrack::traits joins a sample sheet onto the tips by name and draws its columns with their headings whole; the tips it does not name are said under the tree, and TreeTrack::join returns what matched and what was left out on both sides (Traits::join, Join). The command line and the playground join their sheets through it. Traits, Join and Sheet are exported from the crate root, and Sheet::parse reads one.
  • plot_tree() starts a plot of phylogenies with no made-up locus to hide and no ruler, and Plot::add_key() keys every track at the foot of the figure in the figure's theme, as the command line does.
  • The phylogenetics guide opens with eight recipes that go from the files a phylogenetic study has to a figure: a tree read with Tree::parse and drawn with its support, a sample sheet beside the tips, coloured branches and a folded clade, a circle, an alignment in the order of the tree, two trees face to face and a tree against the years. Each is a whole program that compiles and runs, and the rest of the guide, the recipes and the phylogeny track page use the same calls.
  • The tree viewer's canvas draws a column with more levels than colours as shapes, as a figure does, and keys each level by its shape.
  • --recombination FILE draws a recombination rate as a line in cM/Mb, from a genetic map as HapMap or the imputation panels write one, or from a bedGraph of rates; a file whose name holds genetic_map is one when named on its own. read::recombination::rates reads it, and CoverageTrack::axis_title says what a line measures.
  • A table of counts or of estimates whose times have fractions, as a skyline in decimal years, is read as a continuous time to a thousandth of its unit, from nought, where it was refused. Its ruler and its tooltips write each time as the file does. AxisTrack::decimals, time_decimals on the two tracks and read::series::fractional_times are the library side.
  • Seven playground examples, for the tracks that had none: lineages over time, selection along a gene, a nanopore signal, samples in windows, linkage between variants, a contact map and a peak by linkage. The examples use every track flag again, and a test says so.

  • Four tracks the command line could not draw now read the tables their tools write, each its own place, so none needs a region: --frequencies draws counts of each group over time out of a total, as a surveillance programme or an evolution experiment writes them; --phylodynamics an estimate over time with its interval, EpiEstim's table included; --selection a test at each site of a gene, HyPhy's FEL and MEME as they are; and --squiggle the current of a nanopore read from SLOW5, which a .slow5 named on its own is too, with --read naming which read. A table's columns are found by their headers, tabs, commas or spaces apart. read::series holds the four readers.

  • --heatmap FILE draws a value per sample per window, as bedtools unionbedg writes it, and --relative reads each sample against its own median, so a sample sequenced deeper is not a darker row and a deletion is what stands out. MatrixTrack::windows gives a matrix a column per window, each cell as wide as its window, and read::table::windows reads the table.
  • --pairs FILE draws a value between two places, PLINK's .ld, BEDPE or a table headed pos1 and pos2, as a triangle hung under the axis where most places were measured against their neighbours, as linkage and contact maps are, and as arcs where a few pairs join places far apart. --style chooses, --threshold leaves out the weak pairs and --log colours a contact map. PairTrack, Pair, PairStyle, Plot::add_pairs and read::pairs are the library side, and a .ld or .bedpe named on its own is one.
  • --ld FILE after --manhattan colours each point by its r² with the lead variant, and draws the lead as a diamond with its position over it, as LocusZoom does, keyed under the figure. ManhattanTrack::linkage is the library side.
  • AxisTrack::counting is a ruler of whole units that are not bases, with plain numbers in the middle of each unit: under an alignment, a table over time, the sites of a gene and a read's samples the command line draws one and names its unit, column, week, site or sample. A ruler of bases wrote a year as 2,015, and two thousand samples as 2 kb.
  • A sequential matrix keys its ramp under the figure, from nought to the value it saturates at, with MatrixTrack::unit after both numbers.
  • Five pages under Your data, each with its command, its figure and its example files: many samples in windows, pairs of positions, counts over time, selection along a gene and a nanopore signal; and a peak coloured by linkage on the page of an association scan.

  • A comparison is placed on its query by name: karyon asm1_chr1 assemblies.paf reads the length the PAF writes for asm1_chr1, where the place was refused as a name no file gave.

  • Synteny ribbons and dot plots key the two colours they are drawn in, same strand and reversed, and an alignment too narrow for its letters keys the colours of its bases, its classes of amino acid or its one colour of difference. Figure::key gathers them, and the command line draws them.

  • --with-tree FILE after --msa, --snps, --matrix or --domains orders the rows as the tree's tips and draws the tree beside them, as the library has done all along; the command line could not ask for it.

  • An alignment needs no place: karyon --msa aln.fasta draws all its columns, as a tree is drawn without one. It was refused until a region was made up, and the one to make up was the name of one of its rows.
  • An option written after a file that has no use for it, where an earlier file does, is refused with the file it belongs to: --threshold means nothing to a features track; it is an option of gwas.assoc, so write it right after gwas.assoc. args::takes asks the grammar whether a track takes an option.

  • --rename FROM=TO reads a sequence a file calls FROM as the figure's TO, so a PLINK table that writes 1 is drawn beside a FASTA and a BAM that say NC_000962.3, on their length. A file is read by the figure's name first and by a renamed one only where that finds nothing. A figure on a name a file does not use says which --rename would draw it, where one plainly would: the one sequence the file names, or the figure's own with a chr more or less. One name renamed two ways is refused. All three simulated users had to find out by reading the file that PLINK named the chromosome 1.

  • A sequence drawn whole to where its rows reach, because no file gives its length, says so on standard error: all three simulated users saw that the figure stopped at the last tested position only from the corner label. The note says to write the span, or to add a FASTA or BAM, and how to place the figure on their name where they call the sequence otherwise. Files::note is how the figure tells whoever drew it, and Disk::notes keeps what it was told.
  • Two more notes: a BAM named on its own over a thousand bases or fewer says that --pileup draws its reads, since a simulated user drew four hundred bases of depth where the reads were wanted; and bases drawn as blocks say the --width that would letter them. Figure::px_per_bp is the zoom a figure draws at.
  • A file named on its own is a track of the kind its name says, under any .gz: BAM draws its depth, SAM its reads, VCF its calls, GFF3, GTF and BED features, bedGraph a signal, FASTA the reference, Newick a tree, PAF synteny, and a PLINK or REGENIE table a scan. Its options come after it, as they do after a track flag, and a flag in front still chooses the kind. A .bed that is modkit's bedMethyl is drawn as methylation, and one of four columns ending in a number as a signal. A name that says nothing, .tsv or .txt, is refused asking for the track's flag.
  • The place a figure is drawn over may be a word: a gene, found in the figure's annotation by the names GFF3, GTF and BED give it and drawn with a margin of a tenth of its length, titled with its name; or a sequence, drawn whole, as long as a FASTA, a BAM header, a ##sequence-region or a ##contig says, or as far as a file reaches on it. A gene at two places is refused with both, and a name at none with the nearest names the annotation has and the sequences each file names, in a header or on its rows, since the name is most often one file's for what another calls otherwise, as PLINK writes 1. So karyon rpoB reads.bam genes.gff3 calls.vcf.gz is a whole figure. One position, chr1:18,350, is answered with a span around it.
  • Every track given no --label is called after its file: calls for calls.vcf.gz, reads for the reads of data/reads.bam and reads depth for its depth. Standard input, a pipe the shell names, a tanglegram's two trees and a tree get no name: a tree is plain to see, and its file's name was a word by itself in the margin that all three simulated users asked about.
  • A flag another tool spells for something karyon says otherwise, --vcf, --region, --metadata, --legend and some sixty more, is answered with how karyon says it, and any other unknown flag says where the tracks and their options are listed.
  • A key to the colours of a tree's branches and of every --traits strip is drawn under the figure, each level once however many strips show it. --no-legend leaves it out. The three simulated users each drew a tree coloured by lineage and found no way to say which colour was which.
  • A pileup with no --with-sequence reads its reads against the reference the figure's --sequence draws. It drew every read as agreeing, with the reference right above it, which is how one simulated user nearly missed the variant the figure was for.
  • --manhattan reads an association tool's own table by its header: PLINK, PLINK 2, whose header is written behind a #, REGENIE, BOLT, GEMMA, SAIGE and the GWAS Catalog's format, finding the position and the p-value, or its logarithm, by their names, and leaving out a test written as NA. It read two or three columns only, and PLINK's ten had to be cut down first.

  • A file compressed with gzip or bgzip is read as the text inside it, by every track and from standard input: a .vcf.gz, a .gff3.gz or a .bed.gz needs no pipe. The decoder is the crate's own, read::gzip, so the crate still brings in no dependency. It checks every member's CRC-32 and length, and a damaged file is an error naming what was wrong. It was checked byte for byte against gzip -dc on bgzip, gzip -1 and -9, incompressible and concatenated files, at 230 to 500 MB/s.

  • A BAM is read as it is by --coverage, --pileup and --split-reads, through the .bai beside it when there is one, so a figure of one gene reads the blocks that gene is in; without one the file is read from its start and left once the reads pass the window. read::bam counts depth the way samtools depth -a does by default and writes the reads as samtools view prints them, and both were checked against samtools on 300,000 reads with every CIGAR operation, flag and tag type, and a read of 70,000 CIGAR operations kept in its CG tag. CRAM, BCF and bigWig still name the command that reads them. cli::stack::Files is what the builder reads through, any closure from a source to its text being one, and cli::stack::Disk is the one the command line uses.

  • karyon help <track> prints one track's entry and only the options it takes, with a link to its page of the guide, and so does --help written after a track flag. The options are the parser's own answer, asked of it option by option, so the help cannot list one the track refuses. karyon help all prints the whole of the help.

  • A mistyped flag is answered with the one it was probably meant to be: unknown flag --coverge; did you mean --coverage?. And an option may be joined to its value with =, as --label=depth.
  • A file that is not text says what it is and what to write in place of its name: gzip, bzip2, xz and zstd, BAM, CRAM, BCF, bigWig, bigBed and 2bit are recognised by their first bytes, and the message names the command, cut to the window where the tool can do that, as <(samtools view -h reads.bam chr1:1-5000). It said stream did not contain valid UTF-8.
  • A window that holds nothing says what the file does hold, and where: no variants in 1:1-5000, though the file holds 12 on chr1, which is the usual reason, a file that names its sequences one way and a region another.

  • The documentation site draws every committed figure itself instead of showing the file. The page's copy of the program holds the code that wrote each figure, so a figure is drawn in the page's own light or dark and laid out at the width of the column it sits in, which on a phone is the difference between labels at their own size and a 900 pixel figure squeezed to a third of it. A figure over a stretch of genome is dragged along it and zoomed into, down to the bases, with the buttons under it, a pinch or the wheel with ctrl held, every frame the program run again over the new window; a tree, a map, a circle or a sheet zooms as a picture. A mark says what it holds under the pointer, Larger redraws the figure at the width of the window, and SVG saves the view on screen. The gallery's cards follow the page's light or dark too. The file is still in the page and is what shows with JavaScript off. The playground's program gains figure_region, which says whether one figure runs along the genome without building the other forty-five to find out.

  • Theme::mono_family, the font stack coordinates are set in, and in karyon::svg the pieces the new look is made of: text_width_strong and mono_width beside text_width, fit_text_by for text measured some other way, TextStyle and SvgWriter::text_styled for a run of text in another face, weight or spacing, and SvgWriter::fade_down, a fill that fades a colour from the top of a shape to its foot and is written once per colour.
  • CopyNumberTrack and --copy-number, for the segments a copy number caller fitted. A window track fills from its baseline out to the value, so a segment called at exactly the ploidy draws nothing, and that is most of a genome: measured on four windows at two copies, no call, seven and nought with the baseline at two, two marks come out and the two that vanish are the balanced one and the missing one. So a level here is a bar at the level, and only a segment nobody called is blank. --ploidy is required rather than defaulted because where balanced sits is not in the file, and a rule in the wrong place does not mis-scale the ladder, it swaps every gain for a loss.
  • Loss of heterozygosity is unrepresentable as an absence: CopyNumber::Total has no field to put a minor allele in and minor() answers with None, so the finding cannot be confused with a call nobody made. Copy-neutral loss is what the lane along the foot of the band exists for, since two copies both from one allele sit exactly on the rule that means unchanged.
  • DynseqTrack and --dynseq, for per-base model attribution drawn as the bases themselves. It is not a sequence logo, and the reason is measurable: a logo normalises within a column, so 0.1 and 0.9 both come out at height 1.0 under Probability and both at 8.65 under LogOdds, and LogoColumn::add clamps a negative weight to zero before any score is chosen. Three regimes and the zoom picks between them, with no aggregate offered, because a maximum hides a strong negative, a minimum hides a strong positive, and a mean cancels a +2 against a -2 into a nought.
  • The rule under a dynseq track is one line per run of scored bases rather than one across the band. A base scoring exactly nought draws no glyph and so does a base nobody scored, so the rule is the only thing that tells them apart.
  • JunctionTrack and --junctions, for splice junctions as arcs weighted and labelled by the reads that crossed them. Three things separate it from the structural variants that also draw arcs: an intron is the boundary between two bases rather than a base, so the feet are at a base's left edge and the arc meets the step in the coverage under it; an intron reaching further is not a bigger event, so height carries nothing and no value axis is offered; and the count is printed, because the ratio between two junctions is the finding. Thickness is logarithmic, since counts inside one gene span three or four orders of magnitude.
  • read::segments, read::dynseq and read::junction. The segment table finds its columns by name off a required header, because every one of them has a chromosome, two coordinates and some numbers, and reading nMajor where nMinor was written is a figure claiming lost heterozygosity in the arms that kept it. SJ.out.tab counts from one and inclusively on the intron, which is a third convention and neither of its neighbours.
  • Two examples, copy_number and regulation, and the figures they draw.
  • A recipe for the cohort copy number landscape, which is a WindowTrack with two rows over every span. There is no track type for it because that already draws it: Window holds one number per row and nothing says one row per place, so a locus gained in a third of a cohort and lost in a fifth of it is drawn going both ways.

  • Metadata columns beside the rows of six tracks. A track drawn as a row per named thing answers which ones, and the question after it is almost always what they were, which is in a sample sheet rather than in the file the track was drawn from. MatrixTrack, MsaTrack, SnpTrack, CladeTrack, DomainTrack and LocusTrack take Traits now, and draw one narrow strip per attribute in the strip they already reserve for the row names and the dendrogram. A pileup has rows too and they are reads rather than samples, so it does not take one.

  • These are not a track and are not going to be. A sample's lineage is not at a base, so a track of them would need an x nobody has and the first pan would slide a sample's lineage off the end of that sample's own row. They are drawn in the gutter and survive every pan and zoom untouched, which is the whole reason they are attached to a track rather than stacked beside one.
  • read::sheet, which reads a sample sheet: a header, names in column one, and a column per thing known about them. A field is a number when it parses as one, a flag when it spells one, and text otherwise; an empty field, a ., an NA and anything that parses to a number that is not a number are all absent, which is the same three spellings read::table already reads as nothing.
  • --traits <FILE> on the command line, with --columns <A,B,C> to pick the columns and their order. --columns may be written before the --traits it picks from, since two modifiers of one track are not in an order. A sheet naming none of the rows is refused, and so is a column the sheet has not got, which is named alongside the ones it has.
  • The snps example carries the metadata it was always about. Lineage, resistance and collection year sit between the isolate names and the panel, read out of a sample sheet the example prints. Lineage runs in three blocks because the phylogeny put the rows in that order and a lineage is inherited; resistance does not, which is the whole reason two strips are worth reading side by side. The reference and the one isolate the tree does not carry are absent from the sheet, and their cells say so rather than guessing.
  • Each example in the playground carries its own controls, because the figures are not the same thing. A window to slide is what a signal over a chromosome has and a tanglegram has not, so a tanglegram says why instead of offering a slider that would move nothing; a coverage figure gets --aggregate, --style and --log; a domain table gets --analysis; a bisulfite grid gets --context. Every control is a flag, so turning one rewrites that word of the command and the command stays the thing that decides.
  • A window cannot be put where there is nothing to draw. Each example carries the stretch its own files cover and the narrowest window that still draws anywhere in it, both measured by running the binary over that example's own files rather than guessed, and the sliders, the drag and the wheel are all held inside them. Checked by driving every slider of all twenty-one examples to both ends and three points between, 475 positions, and eighty pointer gestures on top: no refusals.
  • Four examples had data too thin to explore. The locus is ten kilobases with something to see everywhere in it now rather than eight calls in the middle, which took its narrowest drawable window from 4,794 bases to 270; the split reads from 4,790 to 200; the structural variants from 38,997 to 2,097; and the pileup down to the floor.
  • Twenty-one examples in the playground rather than six, which between them use every one of the twenty-five flags the command has. Each was run through the binary before it was written down, so none of them is an example that does not draw.
  • The playground's examples open in a panel rather than a menu, because they are pictures and pictures need room. The measurements are the ones the tool it is modelled on uses, since they are good ones: a panel of min(68rem, 100vw) at full height over a dimmed and slightly blurred page, a pill to close, a search across the top, and a grid of cards that lift when the pointer is on them. Escape and a click on the page behind close it.
  • Each card's preview is drawn by the program when the panel opens, out of the same files the example loads. The tool this follows ships a screenshot per example; a picture of a figure is a different claim from the figure, and at a millisecond each there is no reason to make it.
  • The site is laid out the way a tool's site is laid out: a hero, a band of prose, a figure the reader can take hold of, the concepts, what it will not do, and where to go. Five destinations rather than eight, and no file moved, because a page's URL comes from its path and every link that exists points at one of those. Headings are set in their own face and section headings carry a rule, so a page of eight hundred lines can be scanned rather than read.
  • The home page draws its own figure. The picture it ships with is what the printed command actually draws, and the program replaces it on arrival, so the page is correct with JavaScript off, with the wasm missing, and for the moment the fetch takes. It can then be dragged, zoomed with the wheel once it has focus, and moved with the arrow keys, and each of those re-runs the program over the three files printed underneath it, which the script reads out of the page so that what is shown and what is drawn cannot drift.
  • assets/karyon-wasm.js, one bridge for the two pages that run the program. The home page and the playground had a copy each of the protocol, the command line splitting and the region arithmetic, and two copies of a protocol are two things that drift.
  • The playground is a workbench rather than a page: a toolbar, an editor with one tab per file, and the figure filling its own pane, with a splitter between them and a stacked layout for a narrow screen.
  • Figures in the playground can be taken hold of. Interactive makes a drag pan and the wheel zoom, and each of those re-runs the whole program: the region in the command box is what is being changed, so every frame is a figure karyon drew at that region from those files rather than a picture being scaled. A figure of twenty thousand rows over two megabases redraws in about sixteen milliseconds, which is what makes it possible at all.
  • A playground on the documentation site: the command line running in the reader's own browser, over files typed into the page. It is the same code the terminal runs, because it is the same code. cli::stack::build takes a closure that answers with a source's text rather than opening a path, which is what makes it possible: a browser has no disk, so a shell hands the grammar one and the page hands it a textarea. Every flag, every reader and every refusal is what the terminal gives, including the counts, and the figure is a real <svg> element rather than an image of one.
  • playground/, the bridge that runs over. It is a crate of its own and not a member of anything, because it is the one place in the repository that needs raw pointers and the library forbids them, and because doing it with wasm-bindgen would mean a dependency tree for a crate that has none. One buffer in and one buffer out, both length prefixed, and the protocol is written down in the module. The wasm is built by the documentation workflow rather than committed, so the page cannot serve a program older than the library it came from.
  • read::bisulfite reads a bismark_methylation_extractor file, which is one row per cytosine per read. The track is a matrix, one shared list of sites and one call per site per molecule indexed by the site's place in that list, and nothing downstream checks that a caller got it right, so a row built by pushing calls in the order the file listed them puts every call after the first gap one column to the left: a methylation pattern that never existed, drawn as cleanly as one that did. Every row is built at the full width and written into by position. Both mates of a pair are one fragment and so one row, and where they overlap and disagree neither call is kept.
  • read::domain reads an InterProScan table, and its axis is residues rather than bases, which makes it the only track here that is not drawn over a genome. Column one names the row rather than selecting it, as in the locus reader, since the figure is the comparison. The protein's length is column three and is never the furthest domain: a protein whose last annotated domain ends at 300 may run to 800, and a backbone drawn to 300 says the domain reaches the C terminus. hmmscan --domtblout is deliberately not read; which of its columns holds the protein depends on which program wrote it, and the only thing that says so is a comment line that goes the moment output is piped.
  • --bisulfite and --domains, which is twenty-five of the thirty-three track types the command reaches, and eight left in the library.
  • --context and --analysis, which join --modification as the flags that say which of the several things a file holds to draw. All three are one mechanism now, Kind::selector, spelled by what is being chosen and exhaustive so that a track added without one cannot silently stack whatever it found.
  • read::methyl reads bedMethyl, as modkit pileup writes it. Coordinates pass straight through, since it counts from nought like BED. Two things a pileup writes are not measurements and are refused as such: a position with no valid coverage, which the file marks by writing nought in every count and which read as a fraction is a mark on the baseline saying the cytosine is unmodified; and column eleven, the rendered percentage, which is the one field a pileup can write the word NaN into. The fraction is the two counts instead, so a value that is not a number cannot be built. A file counting more than one modification is refused until --modification says which, because m and h at one cytosine stacked on one axis are two marks naming neither.
  • read::structural reads structural calls out of a VCF. POS is the base before a symbolic event, so it becomes the 0-based start unchanged and END the exclusive end unchanged: both conversions are the identity, for two different reasons, and copying the point reader's POS - 1 moves every call two bases left. SVLEN is taken absolute, since 4.3 writes a deletion's length negative and 4.4 positive. A call stating no length at all is refused rather than made one base wide, <CNV> is counted rather than drawn as a duplication, and a breakend finds its mate in its own ALT so that the reciprocal record does not draw the same arc again.
  • read::split reads molecules that aligned in pieces, from SAM and its SA tag. The order of the pieces is the whole claim the figure makes, so it is computed from where each alignment sat on the read rather than assumed. A CIGAR runs along the reference, so a reverse-strand alignment's clips are the far end of the molecule and its place on the read is measured from the other side; without that step every read crossing an inversion comes out in the opposite order and draws the mirror of what happened. Only primary alignments are read, since a supplementary one is already an entry in its primary's tag and reading both counts every piece twice.
  • --methylation, --structural and --split-reads, which is twenty-three of the thirty-three track types the command reaches. --modification names the modification a pileup counted.
  • read::clade reads the GFF3 that recombination detection writes, Gubbins above all, whose ninth column carries the taxa an interval is carried by. It is the one file shape in ordinary use holding a span and its carriers in one record. The list is split before it is decoded, because the ninth column spends the comma on its own syntax and decoding first turns one name into two; a repeated taxon is dropped and counted, because a block claiming more carriers than the tree has rows leaves what it covers to a subtraction with no answer; and a file naming exactly one sequence is read whatever that sequence is called, because Gubbins writes the literal SEQUENCE and filtering on the region's own name would draw a phylogeny with nothing on it, which reads as a statement that there was no recombination here.
  • read::locus reads gene neighbourhoods and the homologies between them. Column one names the genome rather than selecting it, which is the one place an interval file is read differently here, so the file is the concatenation a shell already produces. links takes BLAST tabular output, which DIAMOND and others write too, or two or three columns of names, and does the join a Homology needs: it refers to genes by position and every file that produces one names them instead. The join is exact and every way it fails is counted rather than resolved, since a name pointed at gene nought draws a well formed ribbon into the leftmost gene and takes that gene's unmatched outline off.
  • --clades and --loci, taking their second file by name as --tanglegram does: --with-tree for the phylogeny a clade track is painted onto and --links for what joins one locus to the next. Both are required. Each of these tracks draws a finished-looking figure without its second file and each of those figures says something strong and false, a locus track most of all, which marks every gene no homology reaches and so turns a forgotten flag into the loudest positive finding it can make. That is twenty of the thirty-three track types the command reaches.
  • --identity says whether a homology file's third column is a percentage, as BLAST and DIAMOND write it, or a fraction, as others do. Left out it is worked out from the values and refused where they cannot say, because read the wrong way round every ribbon becomes a perfect match and nothing fails.
  • The command line moved into the library as karyon::cli, and stopped opening files. cli::stack::build now takes a closure that answers with a source's text, so the same grammar that a shell drives from disk can be driven from a browser holding the files in memory. cli::args::Kind::ALL lists the tracks it can draw, for a caller offering a choice of them.
  • Five more track flags. --synteny and --dotplot draw a PAF from minimap2 as ribbons or as a dot plot; --orfs and --logo compute off the same FASTA and aligned FASTA that --sequence and --msa already take; and --tanglegram puts two phylogenies face to face. That is eighteen of the thirty-three track types the command reaches, up from thirteen.
  • read::align_pairs reads PAF. Both of its coordinate pairs are already 0-based and half-open, so it is the one reader in the directory that moves nothing, and a whole-genome file says how many rows belonged to a pair the figure is not about rather than stacking them on one axis in silence.
  • A track flag can now name a second file, for a track whose data is not one file: --tanglegram left.nwk --against right.nwk. The second is spelled by what it means rather than by where it sits, as every other modifier is, and a track that takes one is refused without it, because a tanglegram of one tree against itself has no crossings and no crossings is what a correct answer looks like.
  • An integrated evolutionary-genomics and surveillance vocabulary. Rectangular TreeTrack branches can now be orthogonal, diagonal or curved without changing topology; AncestralStateLayer, BranchEventLayer and BranchIntervalLayer keep state posteriors, direct events and uncertainty on their correct nodes or edges in rectangular, circular and unrooted projections. PhylodynamicTrack adds linear or log trajectories with uncertainty ribbons, while SurveillanceTrack adds observed lineage counts, denominators, stacked or line views, sampling floors and explicit frequency or growth alerts. The evolutionary_surveillance example joins these with molecular selection, genomic layers and a core-versus-accessory tanglegram on one eight-panel sheet.
  • Molecular-selection figures can now preserve the structure of branch-site and site-wise results rather than compressing every analysis into one branch colour. BranchRateMixture draws weighted ω-class capsules on rectangular, circular and unrooted trees; HomoplasyLayer connects equal direct branch events as recurrence candidates; and SelectionTrack aligns p-value or posterior evidence above a signed, neutral-centred site effect. Missing estimates remain missing, source weights and rates stay exact in SVG tooltips, and evidence is never allowed to impersonate effect size. The selection_atlas example combines all three with protein architecture and iTOL-style terminal metadata.
  • Panels::to_svg_with_id_prefix, and Panels is a Drawing. A sheet numbers its panels' ids from p0-, so two sheets inlined into one page, or one sheet made a panel of another, claimed the same ids and the second sheet's clips resolved to the first one's rectangles. The prefix now goes in front of every panel's, and a sheet given one does not name itself, because it is going inside another document. to_svg is what it was.
  • Drawing::region, the region a drawing is laid out along when it has one. A Figure answers with its own when its tracks are measured against it, and everything else, a circle, a map, a sheet or a stack of trees, answers with none. It is what a viewer pans and zooms, and the one question a page could not otherwise ask of a drawing it was handed.
  • Every example builds its figures in examples/figures/, one function per committed SVG, named after the file and taking a theme, a width and a region. The example writes them, and the playground compiles the same files and draws them in the page through two new exports, figure and figures, so the site can show a committed figure in its own light or dark, at the width of the column and over a window the reader moves. Asked for nothing, each is the committed file byte for byte, and a test holds it to that. The literal colours in the examples are entries of the theme's palette, so a dark figure gets the dark palette; example-dark.svg keeps the one it has always drawn.

Fixed

  • A bigWig, a bigBed or a 2bit named for a second file of a track whose own file is piped in, as --manhattan - --ld signal.bw, is answered with the command that writes it as text. Whether the file was a pipe was read off the track's own file, so it was told that a pipe cannot be read that way and to name the file it had named.
  • A FeatureRing name just past six o'clock, with no room for a letter between its feature and the centre line it runs towards, is centred on its arc. It came out as a lone ellipsis, which named nothing: katG on the circular chromosome of example-circular.svg, and left out altogether on the gallery's, which now both name it.
  • --variants draws a call whose AF is ., the missing value bcftools writes for a fraction it could not work out, full height, as it draws a call with no AF: AF=. refused the whole track with AF is not a number: ".". On a row of several alternates AF=0.5,. gives the first its fraction and the second none. --structural reads an END or an SVLEN of . as none, so the other one says how long the call is, or its REF where it is spelled out, where either refused the track the same way.
  • read::bgzf::Bgzf, which read::bam::Bgzf names, goes to a place in a file's first block when that is the first place it is sent: a new reader took itself to be at the end of an empty file there, and refused every place in that block as past its end. A BAM's header is read before its reads, which loaded that block first; the rows of a small bgzipped text file start in the block that holds its header, and are read by a reader of their own.
  • A pipe the shell names, as <(zcat depth.bedgraph.gz), is read from its first byte. Asking whether it was a BAM read its first three bytes and dropped them, which happened for a --coverage track and for every file of a figure placed by a name, so a bedGraph's first row was read on another sequence and drawn as no data, and a GFF3 whose header lost its ##g was not read as GFF3 and no gene in it was found by its name. A BAM handed over that way is read as a BAM, from its start: it was refused as text that is not UTF-8.
  • A figure says each thing it should say once: a BAM a few reads wide over two places said it was drawn as its depth once a panel, word for word. Bases too narrow for their letters in panels of different lengths said a --width for each panel, and the first left the longer places in blocks; a figure of several places now says the one width that letters them all.
  • A pipe the shell names, as <(tabix -h cohort.vcf.gz chr1:1-100000), reached its track short of its first three bytes. Every track that can draw a BAM, and a figure placed by a gene's name, read them to see whether it was one, and a pipe gives each byte once: a cohort's VCF lost the ##f of its first line and was refused for having no #CHROM line, a SAM the @HD of its header, and a bedGraph the first letters of its sequence's name. Only a file on disk is asked now, since a BAM read a window at a time is opened again, which a pipe cannot be.
  • A VCF row at the largest POS a file can write panicked --variants and --genotypes with an overflow, adding to it the length of what REF spells. The end saturates, and the row is left out of any window that ends before it.

  • --variants drew a gVCF's placeholder allele as a call: a GATK row written T,<NON_REF> was a substitution and a second lollipop called non_ref at one base, and a reference block of <NON_REF> or of bcftools' <*> was a call of its own. The placeholder is skipped allele by allele, so a fraction given per allele stays with its allele and a genotype's allele numbers keep naming what they were written against, and a row of only the placeholder is a reference block, as one whose ALT is . is.

  • --matrix given a VCF read its first record as the header of sites and refused it for a word where a position goes, which said nothing of what the file was. It says it is a VCF, and that --genotypes draws its samples.
  • Traits::colors reaches every column of its key, the ones made after the call as well as before it: given before Traits::strips it touched no column, and the strips came out in the palette. It reaches the branches of a phylogeny coloured by the key with no strip of it too, which were dealt the palette whatever colours the sheet's column was given, so a country was one colour beside a matrix and another along the tree above it.
  • The help and the guide said a CRAM named on its own is drawn as its depth. It is taken for one and refused with the samtools depth command that writes the depth as text, since reading a CRAM takes its codecs and a reference; the sentence now names the BAM alone.
  • A page moving a figure rewrote the first stretch of its command spelled like the place, so a --shade or another value written before the place with the same spelling was what moved. It rewrites the place where it stands.
  • A file that is not text, named on its own with no flag in front of it, is answered with its track's flag in what to write in its place, as write --variants <(bcftools view calls.bcf) where its name is for karyon chr1:1-5,000 calls.bcf. Done as it said, the bare <(...) was looked for as a gene or a sequence called /dev/fd/63, and on Windows the bare - was refused for want of a track.
  • A tanglegram names its trees after their files on Windows too: given C:\runs\before.nwk it printed the whole path over the tree, because the name was cut at / alone. It is cut at the separators the system writes.
  • A Newick tree, a SLOW5 file or a move table that starts with a byte order mark is read, as every other file already was. Windows tools and spreadsheets write one, and a tree read "more than one root" at character 2, a SLOW5 file "a raw sample is not a number" on line 1. The mark is dropped once where the command line decodes a file, and by every Newick reader of Tree, read::series::squiggle and read::series::moves for a library caller.

  • A tree beside an alignment, a matrix, a panel of variable sites or a domain panel is cut to the rows it has. Drawn whole, a tree with a tip the panel lacked put every row after that tip beside the branch of the tip before it and the last branch below the last row, with nothing to say so; the band now says how many tips have no row. A row hidden under a cap goes from the tree too, so no branch leads off the band. Tree::keep_tips is the cut, as ape's keep.tip makes one.

  • The tree beside a matrix with metadata strips starts level with the rows, under the strips' headings; it sat a heading's height above them.
  • A tree says what it was asked for and does not draw, in lines under it, and TreeTrack::warnings hands them to a caller; the command line prints them. A reroot, fold or highlight naming a node the tree does not have, or a tip, an outgroup that is not one clade, a midpoint on a tree without lengths, a key no node carries, a support threshold with no support style, a negative dN/dS significance, a time axis some tip has no date for, branches that run backwards in time, branch colours shared by two values and support above 100 each drew a figure like the one asked for, or like the plain tree, with nothing to say so.
  • A panel of variable sites drawn beside a tree no longer says its reference sample has no row. The reference is drawn on the row above the tree, and it was counted among the tips without one.
  • A tree's settings draw one figure in either order. A radial start, sweep, direction or inner radius turned a tree chosen as unrooted or rectangular into a circle, circular undid a fan, a scale bar's length or unit brought back a bar hidden with show_scale_bar(false), dnds and color_by each cleared the other so the last one won, and a tanglegram's show_tips put the names back on both sides. A projection chosen by name is the one drawn, a hidden bar stays hidden, and dN/dS colours the branches whichever came first, with the band saying the other colouring was not drawn.
  • A tree whose time axis cannot be drawn keeps the scale bar its branch lengths need; drawn by branch length, it had lost both.
  • A fold or a highlight asked for before a reroot follows its clade through it. Rerooting turns edges round and keeps every node where it was in the list, so the index named another clade: a fold of one lineage folded twenty-eight tips of three. A clade the new root splits is said instead.
  • A tanglegram coloured by an annotation deals its colours in the order the left tree meets the values, as the tree coloured by the same key does; it dealt them in the order the values sort, so a tanglegram and the tree beside it painted one country two colours. A tie whose trees disagree is drawn in the foreground ink, where it took the palette's second colour and looked like the second value, and TanglegramTrack::legend keys the values and the disagreement for Figure::key, where the tanglegram had no key.
  • A tree keys every layer it draws. The chips across the top of its band, for node glyphs, dN/dS, rate classes, branch events, intervals and homoplasy, ran along one row and the ones past its end were dropped: at 500 pixels two of six glyph layers had no key and a third was cut to its first letter. They now run onto as many rows as they need, and the band grows to hold them. A layer of branch events or intervals on its own had its room held and no chip drawn in it. The chips keep to the left of a rectangular tree's column headings and under a circular tree's ring headings, where the first chip covered the first heading.
  • The columns of words on a phylogeny each take a stretch of the palette, as a sheet's columns do. Two strips added with trait_categorical both started at the palette's first colour, so each lineage was also a country. A column that chose its start with first_color, as every column from a sheet does, keeps it, and branches coloured by a key no column shows take a stretch of their own.
  • A strip of words with more levels than the palette has colours is drawn as symbols, beside a tree or any other track, and keyed with them. Drawn as asked, a column of seven countries painted two of them one colour; only Traits::spread chose symbols for such a column.
  • A key draws each level the way its column draws it. A symbol column was keyed as boxes, so two levels sharing a colour were two entries nobody could tell apart, and a binary column was keyed in two palette colours neither of its dots was drawn in; it is now keyed present and absent with the two dots. A column over the key the branches are coloured by keys them under its own heading and in its own marks, where the column went unkeyed as a repeat.
  • A symbol column tells as many levels apart as it says. Theme::symbol wrapped with the colour every twelve levels of the six-colour palette, so the thirteenth level was drawn exactly as the first; with Theme::color of the same index it now tells four times the palette's length apart, from whichever colour a column starts at.
  • Figure::key names a phylogeny's colours, in the figure's own theme. TreeTrack did not implement Track::key, so a figure's key left the tree out, and a key built by hand from the default theme named each level of a dark figure in the light palette's colour.
  • The heading of a ring around a circular or unrooted tree is marked in the ink of its text. It was a chip in the palette's first colour, which is the colour of the first level, and read as a key to it.
  • A tree's support is read out of a hundred or out of one once for the whole tree, out of a hundred when any value runs above one. It was read value by value, so a clade at 1 on a bootstrap tree was drawn as full support, at the largest size and past any threshold asked for.
  • --features draws a gene once. An annotation writes it at every level, and each was drawn as a feature of its own: NCBI's five rows for one gene came out as the chromosome, named ANONYMOUS, the gene, its transcript, two exons and the CDS. A GFF3 row whose Parent= or Derives_from= names a row in the file is now left out, so is a region, chromosome, scaffold or similar row from base 1 that describes the sequence itself, and a part whose whole is not in the file is still drawn. GTF is read with its own names, gene_name and gene_id, where every GTF gene was drawn nameless beside its own CDS.
  • --manhattan reads a column of p-values as one. A table headed P, pvalue, p.value, p_wald, P_BOLT_LMM or another name every association tool writes, or a q-value or FDR column, is drawn as -log10 of itself with the axis saying so, where it was drawn as written: the strongest hit sat on the floor of the scan, the null at the top, and the command exited nought. --threshold is then a p-value as well, since it is given in the file's units, so 5e-8 draws the line genome-wide does and 7.3 is refused. A table with no header whose every value lies between 0 and 1 is refused, asking for a header, since nothing in it says whether it holds p-values; a name mentioning a logarithm is drawn as written. genome-wide is refused on a phylogeny, where it was read as a support value of 7.3. read::point::association_table returns the points and what they were.
  • A FASTA record cut out by samtools faidx, headed >chr1:101-160, is read where its header puts it. It was read from base 1, so a window on exactly the bases it held drew an empty band, and any other window drew the wrong bases, without an error. A header is read as a span only when the span is as long as the record, and several slices of one sequence are picked by the window.
  • --sequence, --orfs, --dynseq and the reference a pileup reads refuse a record with no base in the window, saying which bases it holds. The track was drawn empty and the command exited nought, and a pileup whose reference was elsewhere drew every read as agreeing with it. A window that runs past the end of a record still draws the bases there are.
  • A command line that gives one option two values is refused, naming the flag and the track, where the last value won without a word: a --label meant for the next track and written before its flag renamed this one, and a second --height undid the first. --highlight, which adds clades to a list, and the flags that only switch something on still take any number.
  • -o refuses a name that promises a format karyon does not write, such as fig.png, fig.pdf or fig.svgz, and says to write SVG and convert it. The figure was written as SVG under the name, and the command exited nought.
  • --height takes a number of pixels above nought, as --row-height already did. NaN parsed as a number, so a track given it shrank to its floor and the figure exited nought without it; an infinity, nought and a negative height drew nothing.
  • A figure made only of --tree, --tanglegram and --snps tracks takes no region. None of them is drawn in a window, so the command line made a reader invent one, tree:1-1, which the figure then printed as its locus. The library draws no locus above a figure where no track shows the window, and keeps it out of the figure's accessible name and description: example-snps.svg and example-tanglegram.svg lose sites:1-34 and taxa:1-8 from their <title>, and nothing drawn changes. An ideogram marks the window on its chromosome and keeps its locus; Track::shows_region says which tracks do. A region given to a tree is still accepted.
  • A level of a sample sheet is one colour in every strip of a figure and in its key. The strip beside a tree dealt the palette in the order the tree met its tips and the strip beside a matrix in the order the sheet sorted its names, so one sheet drawn beside both put each lineage in a different colour on each side. Every column Traits makes now carries the order its levels are coloured in, Traits::from_sheet takes that order from the file, so a sample appended at the end repaints nothing, and a tree handed the column deals its colours the same way; --traits does all of this. Sheet gains order and levels, and TraitColumn gains levels and level_order.
  • TreeTrack::legend keys a tree's branch colours and strips from the count they are painted from. The only key there was, Traits::legend, numbered the levels the sheet's way, so a figure of two countries printed each one's colour beside the other's name. A tree now counts over every node rather than the ones on screen, so folding a clade no longer repaints the rest: example-phylogenetics.svg drew the same sample in two shades in its open and its folded panel, and draws it in one.
  • Traits::legend draws a ramp in the theme's colours and labels it with the column's range. It passed the two the other way round, so the ramp was painted with fill="48.2" and labelled with two colour codes. A continuous column with no number in it is left out of either key rather than labelled with the placeholders an empty count starts at.
  • A figure's width floor grows with visual_scale, as the margins and the label gutter it adds up do. It added them up unscaled, so at visual_scale(2.0) a figure held to its floor was 234 pixels wide with its plotting area starting at 332, and the track and its name were drawn off the right of the image. Nothing changes at the plain scale, and every committed figure is byte for byte what it was.
  • --methylation draws a window whose every position went unmeasured, with their number in the band's corner, rather than refusing it. It was refused as holding no modified bases though the file held some, which reads as though the calls were somewhere else, when they were in the window with no valid coverage. A floor from --min-reads that hides every call already drew the band with its count, and a window with nothing in it at all is still refused.
  • HomoplasyLayer reads a list annotation one event at a time, as BranchEventLayer already did. It read the list whole, as the text it prints as, so a branch carrying {S45N,E88K} and one carrying {S45N} held two different events and were never joined, while two lists of one change were joined under a tooltip reading {S45N}. A change listed twice on one branch is still one branch, and is not joined to itself. Every committed figure is byte for byte what it was.
  • A phylogeny's settings count the same in whatever order they are written. time, dnds and branch_labels started their layer afresh, so a unit, a direction or a hidden axis written before time, a dnds_ setting written before dnds and a label size written before branch_labels were dropped without a word. A reroot turned the root marker back on after show_root(false). And max_rows folded clades when it was called: a reroot written after it kept the folds of the shape it replaced, which are the wrong clades once the tips have moved, a collapse after it folded one clade more on a tree already fitted to the cap, and max_rows(None) after it lifted the cap and kept every fold. Each setting is now kept on its own, and the layers and the folds are put together when the tree is drawn. PhyloMap dropped a direction or a unit written before time the same way, and no longer does. Every committed figure is byte for byte what it was.
  • ManhattanTrack keeps one point of each look on each pixel instead of drawing every test. A point drawn over one of its own shape and colour on the same pixel adds an element to the document and nothing to the picture, and a hundred thousand tests over a megabase were a hundred thousand elements and 5.9 MB; they are now eleven thousand elements and 0.6 MB, and a million tests are 0.97 MB rather than 59 MB. The point kept is the last one drawn there, so what was on top stays on top, and every point left out is less than a pixel from one of its own look. example-genomewide.svg and the whole-assembly panel of the gallery lose the background points that sat on a pixel with one like them, and look the same. A tower of hits packed onto a few pixels shows more of the significant colour than it did, since fewer rings in the page colour are drawn over it.
  • The documentation of read::point::associations said the value was a p-value the track would put on a log scale. The track draws it as given, as the format guide and --threshold already said, so a table is written as -log10(p), and Association::from_p_value converts one.
  • Three playground examples drew something other than what they said. The modified-bases example wrote 1 as the modified count of every row, and the reader takes the fraction as that count over the coverage, as modkit writes it, so a site generated at 88 per cent read "3% modified in 40 reads"; the counts now carry the fraction. The split-reads example offered a --height control that the command line refuses for that track, so every choice failed, and it is gone. The variable-sites example offered a window to move along, which the panel does not draw on, and now says so instead.
  • The crate and the site say what the readers and the drawing do. The crate documentation said karyon does no I/O beyond save_svg, while the command line's cli::stack::open_from_disk reads paths and standard input; it now says drawing does no I/O and names that one function. Its list of what read takes had stopped at nine formats and now names the rest, from PAF and bedMethyl to InterProScan's table. The formats page said --coverage and --windows refuse a value that is not a number, and nan and inf are read as missing and leave a gap; it says so. add_windows offered bars, which WindowStyle does not have.
  • A line stops at a missing value and starts again after it, rather than running straight across. A coverage track in the Area and Line styles joined the last column before a gap to the first one after, so a hundred bases of NaN in a three hundred base profile, or a bedGraph row reading nan, came out as a 282 pixel segment at a depth nothing measured, filled underneath in the default style, while Bars and the track's own documentation left the stretch empty. The same bridge crossed a window with no value or a stretch nobody windowed in a WindowTrack drawn as a line, samples with no current in a zoomed SquiggleTrack trace, an estimate a PhylodynamicTrack could not place on its axis, and, in a SurveillanceTrack, a time whose every row was unreadable as a frequency, straight through the diamond saying so. A value standing alone between two gaps is now drawn across its own width, because a line of one point draws nothing: one column of depth in view used to leave the band blank. Every committed figure is byte for byte what it was, since none of them has a gap in a line.
  • Aggregate::Max no longer says it is the one for dropouts. The covered bases beside a dropout narrower than a pixel column still set that column's maximum, so it hides exactly that; Aggregate::Min shows it, which is what the module documentation and the site already said.
  • A figure's width is held to its floor when the figure is laid out, not when width is called, so a margin or a label gutter counts the same written before width as after it. The floor is the smallest width that leaves a plotting area, and it was worked out from the margins and label_width as they stood at the call: .width(200.0).label_width(150.0) drew a figure 200 pixels wide and .label_width(150.0).width(200.0) one 234 pixels wide, through plot() as well, and a Panels sheet took whichever it was handed. The default of 900 is held to the floor too, since it is set before everything else, and a 1,000 pixel gutter used to put the plotting area off the right of a 900 pixel image unless width was called again afterwards. A label_width that is not finite is taken as zero, as a margin side already was, since an infinite gutter made the floor infinite and the document went out as width="0". Every committed figure is byte for byte what it was.
  • A panel of variable sites counts from one, as the ruler, the locus in the corner and every other tooltip do. Its column labels and tooltips printed each position as stored, so on the command line the first column of an alignment was labelled 0, and the snps example labelled 1472251 a site a VCF writes at 1,472,252. SnpSite::position is 0-based like every other position the crate takes, so a site built by hand with the number to be printed now prints one more than it. The panel also answers false to Track::on_coordinates, and so do IdeogramTrack and LegendTrack, since none of the three is drawn on the window the ruler measures: plot() and the command line gave a panel of sites, an ideogram or a key on its own a ruler numbering that window, and now leave it off, so --no-axis is no longer needed under --snps. A figure that also holds a track on the coordinates keeps its ruler. In the committed figures only text changes: every column label and site tooltip in example-snps.svg, and in the variable-site panel of gallery.svg, is one higher.
  • Six command line flags that were accepted and did nothing now do what they say or are refused by name. --carrying without --mutations put its question to an annotation named after the change, which no node carries, and drew the tree with nothing marked; it is refused, since only --mutations names the key the changes are kept under. --mutations without --carrying read the changes and drew nothing with them; it is refused the other way round, since only --carrying asks anything of them. --color-by with a key no node carries drew every branch in one colour; it is refused with the keys the tree does carry. --height after --copy-number was parsed and never passed on, so the band came out at its own 74 pixels, byte for byte the figure without the flag; it is applied. --format after any track but --coverage, --features and --loci, or with a word the track before it does not read, such as depth after --features, parsed and went nowhere; both are refused. And the positions a bedMethyl holds with no valid coverage, which the reader skips and counts rather than drawing at nought per cent, are now counted on the band beside the calls under the floor, through the new MethylationTrack::no_coverage.
  • --sequence and --orfs take a FASTA's record by the sequence the region names, as --with-sequence already did, rather than whichever record came first in the file. Handed a whole genome, a region on chr2 drew chr1's bases and read chr1's reading frames, above a pileup of the same command compared against chr2, and the figure looked right at every base. A file of one record is still drawn whatever its header says. A file of several that names none of them is refused with the names it does hold, as --sequence genome.fa has no record called chr2; it has chr1, chr3, and a name two records share is refused rather than taken the first of; --with-sequence refuses in the same words now, where it said only that no record named anything in the sequence the region names.
  • Documentation that contradicted the code now says what the code does, and nothing drawn changes. Theme::muted claimed the track labels, which are set in the foreground ink, and now claims the tick labels it does colour. BaseColors::colorblind_safe said green had come to mean adenine, when adenine is green in both sets: what moves is the shade, thymine furthest, from red to vermillion. The crate documentation listed Newick among the formats of read, and it is Tree::parse_newick that reads it. The structural VCF reader said taking one off POS would move every call two bases, where its own audit test shows one. CodonTrack::show_start said its chevron points the way the sequence is read, and it points out of the sequence, away from codon
  • The gallery example said every track type has a panel, and seven have none; it now names them and the examples that draw them. CONTRIBUTING said CI runs only by hand, and it runs on every pull request and on main after a merge. BuildError::Unnamed said it was a column the sheet has not got, and it is any name the file has not got: a clade, a tip, a change, an annotation, a FASTA record or the row --compare-to names.
  • A list of changes written in braces is read whole. Mutations::read took [&muts={A123T,S:D614G,C241T}] back as the text it prints as, braces and all, so the first piece began with one and the last ended with the other and neither read as a change: a list of three came back as its middle change, a list of one or two as nothing, and the same list in quotes read in full. --carrying then refused a change the tree does carry, naming only the middle ones as what it had. The items are read one by one now, and a piece that is not a change is still skipped rather than failing the tree, but it is counted by Mutations::unread rather than dropped without a word. Mutation::parse_list takes braces as separators too, so the text a braced list prints as, {A123T,S:D614G,C241T}, reads as all three changes rather than the middle one.
  • A dynseq band shorter than its own labels no longer panics. The two bounds of the clamp that keeps a label inside the band cross once the band is shorter than the text is tall, and clamp panics rather than choosing, which a track four pixels high reached without asking for anything unusual.
  • A bedGraph of per-base scores is read as spans rather than as one entry per base, so the cost is the width of the window rather than the rows times the width of the window. A sixty byte file across a chromosome asked for seven and a half gigabytes; a thousand such rows asked for about four terabytes and were killed. A thousand rows now cost what one does.
  • The rule under a dynseq track is written as pixels rather than as runs of bases. A file scoring every other base over a megabase produced half a million line elements and forty-two megabytes of SVG to draw a rule; the same input now produces twelve.
  • The strip beside a dynseq track is measured over every label that goes in it rather than over the nought alone, so a score of minus twelve thousand is no longer printed with its minus sign cut off by the clip, which made it a positive number an order of magnitude too small.
  • Junction lanes are packed over the window rather than over the whole track. A junction a megabase away took a lane, and the arcs on screen were flattened to make room for it: one visible arc went from forty-nine pixels tall to two and a half when twenty off-screen junctions were added.
  • The rungs of a copy number ladder keep thinning past a step of ten, so a ceiling in the thousands no longer paints the whole band in the rule colour.
  • The distinct sample names of a segment table are found with a set rather than a linear scan. Sixty thousand distinct names took tens of seconds and were quadratic; they take a quarter of a second.
  • CopyNumberTrack::legend takes the theme and names the inks that were drawn, rather than three literals that are the wrong colours in a dark figure and that claimed a gain and a loss whatever the data held.
  • A copy number rung label is held inside the band at the bottom as well as the top, since with the allele lane hidden the nought label fell below the band and lost its lower third to the clip.
  • An allele pair with one number missing is no longer read as a total of the other. A caller reporting three major copies and NA for the minor has not said there are three copies, it has said the total is three plus something nobody measured, and keeping the survivor as a total put that something at nought.
  • --sample on a segment table with no sample column is refused rather than accepted and ignored, which drew the whole table under a name the command had asked to pick out of it.
  • A --dynseq reference holding several records is picked by the sequence the region names rather than by being first in the file. One chromosome's letters under another chromosome's scores is a figure that is wrong at every base and looks right at all of them.
  • A --dynseq track is laid over the window rather than over the reference, so a score inside the window is not dropped because the FASTA stopped short of it.
  • A segment table spelled start_pos and end_pos is read as 1-based like the startpos it is an alias of, rather than as 0-based, which put every segment a base to the right and stepped over the guard against a start of nought.
  • A row whose end is one below its start is refused in a 1-based table too. The comparison happened after the conversion, so that one row survived it and became a segment covering no bases.
  • --height is accepted on --copy-number and --dynseq, both of which size themselves by a field and were refusing a flag that means something to them.
  • --ploidy 0 is refused. At nought copies a log ratio becomes nought everywhere, and every one of them lands on the rule that means unchanged, so a called amplification drew as a quiet arm.
  • A dynseq band in which nothing was scored no longer prints a scale, which put numbers on the page for a measurement that was never made.
  • A copy number track built by hand with no ploidy is refused rather than given a diploid ladder whose rule came from nowhere.

Changed

  • The quadratic both arc-drawing tracks spring from a baseline lives in one place now, track::arc_path, and the structural variants call it. Verified by re-rendering every committed figure: byte for byte what they were.
  • TraitColumn, TraitScale and TraitStyle moved from track::tree to track::traits, unchanged, and are still exported from karyon, karyon::track and karyon::track::tree. They stopped being a phylogeny's business when six other tracks started drawing them, and the tree now draws its own through the same function the others do, so a lineage cannot be one colour beside a tree and another beside the matrix under it. Every committed figure is byte for byte what it was.
  • Two counts in the prose were wrong before this and are fixed with it: the command line reaches twenty-five track types, not eighteen, and --help prints twenty-four track flags and the ruler, not twelve.
  • Release builds now check arithmetic for overflow. A wrapped add is a figure that is quietly wrong, which is the one thing this crate is not allowed to be: Genome added lengths without saturating, and a release build turned that into boundaries running backwards and every point drawn on the wrong sequence, with nothing said. A branch per arithmetic operation is nothing in a crate whose work is string formatting, and it converts the whole class from silent to loud. A hundred and fifty thousand generated figures and every example render clean under it.
  • Nine functions turned an f64 into text; there are now three, and the two that remain outside svg are four-line wrappers over them. Two of the copies were literally identical and a third was the same plus a check for NaN that the others never received, which is what a copy costs: a fix reaches the copy the bug was found in and stops there. The same mistake had by then appeared three times in three modules. svg::text_rounded and svg::text_exact are the two ways a number reaches a reader, and both carry the guards; svg::num is unchanged and remains the way a number reaches an attribute. svg::text_number, added earlier in this same unreleased version, is gone in favour of text_rounded.
  • svg::finite_within replaces the two private helpers, one in the tree track and one in the map, that differed only in whether the upper end was named. Almost every setting a drawing takes is a length, an angle or a fraction and none of them has a reading for NaN, so there is now one answer to what happens then. Four pixel thresholds that had no answer at all, in LogoTrack, PileupTrack and SequenceTrack, use it.
  • The three largest files became directory modules, split along what they actually do rather than at a line count. src/track/tree.rs was 4,884 lines, two and a half times the next largest, and is now seven files: the three projections apart (rectangular, radial, unrooted), the layout they all read (scene), what is drawn on top of any of them (decorate), the two ways a branch length is given a unit (scale), and the type and its vocabulary in mod.rs. src/map.rs became the plain map, phylo, the marks both compositions share (draw) and the projection maths (project), which is worth its own file because every function in it can answer that it will not place a point. src/tree.rs gave up its Newick, annotated Newick and NEXUS parsing to tree::parse, which is where all the format's ambiguity lives. Nothing else moved: the remaining files are one subject each, and splitting a coherent module into thirds makes it harder to read, not easier. Every public path is unchanged, and all forty-one committed figures render byte for byte the same, which is what makes this a move rather than an edit.
  • The format readers moved from the command line binary into the library, as karyon::read. Four thousand lines that parse BED, bedGraph, GFF3, VCF, SAM, cytoBand, samtools depth, FASTA and Newick were reachable only by running the command, so writing Rust against this crate meant writing a VCF parser first, which is the opposite of what a crate that ships twelve file-backed tracks should ask for. The rule that made them live there is unchanged and now says something sharper: every reader takes a &str, so nothing in the library opens a path, and the binary keeps the one part that is genuinely the command line's. Format moved with them, since it names file formats rather than flags.

Fixed

  • A modifier set from a control was appended to the end of the command, so it landed on whichever track happened to be last: --aggregate set on a figure whose last track was variants was refused, correctly, as meaning nothing to one. A modifier describes the track before it, so it is written next to that track.
  • Closing a file tab wrote the closed file's text into whichever file took its place. The editor remembered which file it held by position, and removing one shifts every file after it down, so the save that runs on the way out landed on the wrong object: closing depth.bg left genes.gff3 holding a bedGraph. It remembers the file itself now, so a file that has gone is not written to and one that has moved still gets its own text.
  • The splitter announced itself as a separator with a value and answered to no key at all. Arrows move it, Shift moves it further, Home and End take it to its ends, and it reports where it is. Its orientation follows the layout rather than being frozen at whichever one the page opened in.
  • Interactive turned on a pan and zoom no keyboard could reach: only the home page's figure had keys. The playground's figure joins the tab order while it is interactive and leaves it when it is not, and answers to the same keys. Its wheel now waits for focus too, so a wheel over it scrolls the page.
  • A refusal in the playground was announced to nobody. The status line is a live region, as the home page's already was.
  • A file could not be removed without a pointer, and the close control being a span inside the tab made every tab's name read as "depth.bg times". It is a button with its own name, the tab carries its own, and F2 renames a file and Delete removes one.
  • The grabbing cursor vanished a pixel into a drag, because a redraw rewrote the whole class attribute and took pg-dragging with it.
  • A region the pan and zoom arithmetic was happy to reach stopped the program dead, and the page said nothing. MAX_SPAN was 1 << 32, which the comment beside it called "below the point where the buffer cannot be allocated"; thirty-two gigabytes is above that point on every target there is, and on the 32-bit one the documentation site runs on, a Vec cannot exceed isize::MAX bytes, so a per-base track of 1 << 28 bases is a capacity overflow, which is a panic, which in a build that aborts on one is a trap with no Result to carry it. Measured: a span of 268,435,455 draws and 268,435,456 stops the program. The limit is that number now, which leaves every chromosome anyone sequences inside it, and says why.
  • Nothing caught that trap, and both pages write the new command down before they draw, so twenty-two turns of the wheel left the command box saying chr1:1-271,050,503 over the previous figure, the previous region readout and the previous timing, with nothing marked as failed. The page asserted a region it had never drawn, which is this crate's own bug class in a browser. A trap is an ordinary refusal now, and the wheel cannot reach a span the program will not draw.
  • Without the program, the example panel came up as twenty-one empty boxes, which reads as twenty-one figures that failed. The boxes go and one sentence says why; the cards keep their command and their files, which are still worth reading and still run in a terminal.
  • Dragging a figure towards the first base of a sequence shrank the window instead of moving it. The start was clamped to 1 and the end left where it was, so from chr1:1-1,000 five drags gave 700, 490, 343, 240 and 168 bases and the figure zoomed itself in while the reader was only moving it sideways. The span asked for is kept and the window slid. The same clamp stopped a zoom out from ever reaching its own limit.
  • A --label that looked like a locus, written before the real one, was taken for the region, so a drag rewrote the label and left the figure where it was. The four flags that stand alone are known now, and the word after every other flag is skipped, as the parser itself does.
  • The example panel said aria-modal and did not mean it: with it open the page behind was still reachable by Tab. It is a real dialog opened with showModal, which contains the focus and makes the background inert.
  • The Examples button went on saying it was expanded over a panel that had closed, because the bookkeeping hung off the dialog's close event, which a listener added directly in front of a close() call was measured never receiving. It is done where the closing happens instead.
  • On a 375px screen the toolbar wrapped but the groups inside it did not, so Full screen was drawn from 326 to 426 with the window ending at 375 and nothing scrolling: the button could not be reached at all.
  • The example previews were drawn on the next animation frame, which a tab that is not on screen never gets, so a panel opened in a background tab came up with every card empty. They need no layout, so they are drawn as the card is made.
  • The example panel came up cream in the dark scheme, with light text on it. Material writes the palette onto body and the rule was written against :root, which is the same mistake this stylesheet had made once before.
  • The playground's first example refused on the fourth drag. Its three rows covered the region exactly, so moving the window took it off the data, which is the program being right and the example being too small to show anything else. It is six bedGraph rows and eight calls over ten kilobases now, and the home page says out loud that a window taken far enough will always find nothing, and that what comes back then is a sentence rather than an empty figure.
  • A wheel over the figure no longer eats the page's scroll. It zooms only once the figure has focus, which is also what makes the arrow keys reachable without a pointer.
  • A GFF3 span whose end came before its start was read as a one-base feature at the start rather than refused. The module doc had said all along that an inverted span stops the read, and only BED and cytoBand did it, so a gene written 400 100 was drawn confidently three hundred bases from where either number put it.
  • A clade block naming one taxon twice counted two carriers of one row, so the rows it cuts out came out of a subtraction with no answer: a panic where this crate checks its arithmetic, and a number near the top of a u64 in a release build that does not. Carriers are distinct rows now, which is what the figure draws either way.
  • A track whose second file was missing was refused by a match with a fallback arm, so a track added without a spelling would have drawn without a file it cannot do without. It is exhaustive now, as the flag spellings next to it are.
  • A value that is not a number was drawn as the strongest one on the page. mix blends two colours by an amount, clamp propagates a NaN, and NaN as u8 saturates to nought, so an amount that was not a number left the ramp entirely and came back #000000: darker than the dark end, on scales where darker means more. Every ramp in the crate goes through that one function. A homology of unknown identity was the visible case, drawn blacker than a perfect match.
  • Homology::identity is Option<f64> rather than f64, matching AlignmentBlock. Two genes matching with nobody stating how closely is an ordinary case, and Homology::new now records a NaN as the absence it is instead of clamping it into the range. Homology::unstated says it outright. An unstated identity draws at the pale end of the ramp with a dashed edge, so the figure distinguishes "nobody said" from "barely alike" without anyone having to point at it.
  • AlignmentBlock::identity had the same hole and is closed the same way.
  • A tree annotation whose number is not a number printed as NaN in every tooltip, trait strip and node label, through the one function the crate had already centralised annotation formatting in. It is spelled with the same glyph an absent annotation already used, since a number field that is not a number and a field that is not there are the same fact about the same field. The infinities are untouched: a file can carry a number too large for an f64, it parses to one, and reporting it verbatim says what the file said.
  • tests/properties.rs checks tooltips as well as attributes. The writer's guard covers numbers written into attributes, and a tooltip is a caller's number put through format! into text a person reads, which is the gap all of the above came through. The property found the tree annotation case on its own, in a track nobody was looking at.
  • A track flag written without its path named the wrong flag. The spelling was kept in step with the list of tracks by a fallback arm rather than by the compiler, so --synteny with nothing after it reported that --axis needed a value. It is an exhaustive match now, and the help text is checked against the parser's own list of tracks instead of a copy of it.
  • A tanglegram summarising one crossing said 1 crossings.
  • SquiggleTrack never finished drawing over a wide region. The envelope branch walked the axis positions a pixel column covers rather than the samples in it, so over a chromosome each column asked thousands of bases whether they happened to be a sample, and over the whole coordinate range it asked billions and the figure never came back. The walk is now cut down to where samples can be before it starts.
  • Genome added lengths and gaps without saturating, in all seven places it accumulates. A debug build panicked; a release build wrapped, which is worse, because boundaries then came back decreasing and every point in a genome-wide plot landed on the wrong sequence with nothing said.
  • A figure whose tracks each reported a height that was a number could still report a total that was not. The height check reads one track at a time, so two finite heights adding to infinity passed it, and the document then said height="0" while Figure::dimensions said infinity: the file and the API disagreed about the same figure. The total now has a ceiling at the largest integer an f64 holds exactly.
  • Tree scale bars, support labels and clade support tooltips printed a large value as three hundred digits. They were using num, which writes a coordinate and stays plain on purpose. svg::text_number is the sibling of it for numbers a person reads, agreeing with num to the byte below two to the fifty-third and switching to an exponent above it. This was the third place the same mistake appeared, after VariantTrack and Map, which is why it now has a function of its own rather than a third patch.
  • A map tooltip printed f64::MAX as three hundred and nine digits. The same bug as the one fixed in VariantTrack, in the other writer, found because the properties had never been pointed at a map: data_number handed the value straight to a formatter, which expands it in full. An f64 stops holding consecutive integers at two to the fifty-third, so past that all but the first sixteen digits are the formatter filling in a gap rather than anything measured, and the same happens under a thousandth as a run of zeros. Outside that range the value is now written as an exponent, which says the same number and says how much of it is known.
  • Six committed figures disagreed with the code that draws them: the MSA row labels had moved to full ink, four phylogenetic figures had shifted geometry, and the gallery had grown. A seventh, the node faces figure, reached the documentation without its assets/ original ever being added. The staleness gate could not have caught that last one, because git diff compares against the index and a file that was never added is not stale to it, it is invisible; the gate now stages the renders first.

Added

  • A branch-wise dN/dS visual grammar for TreeTrack. dnds uses a colour-vision-safe logarithmic scale centred on ω = 1 instead of stretching the observed minimum and maximum across a generic ramp. Values are direct branch annotations and never inherited; missing or invalid fits are dotted. dnds_significance can use a p- or q-value to carry evidence through stroke weight while colour continues to carry effect size. Rectangular, circular, unrooted and cladogram examples share one explicit legend and exact SVG tooltips.
  • The generated figures now reach all thirty-three track types, not sixteen, and build the tree ones through their whole builder rather than their constructor. The fourteen original gaps had never been given a hostile input and eleven numeric setters had never been given a hostile number; four bugs came out of it, each one now pinned by a test that was watched failing first.
  • A property over every reader at once: the same interval written as BED, GFF3, cytoBand, SAM, bedGraph, samtools depth, VCF, an association table and a genotype matrix has to come back as the same two 0-based numbers. The audit beside the readers already pins every format to one known base; this asks the same question without picking the base, because a reader can be right at position 100 and wrong at 0, and because two readers disagreeing is what an off-by-one becomes once a figure stacks them. Every one of the six places the crate converts from 1-based was shifted on purpose to confirm the property notices. It also checks that naming a format and letting the reader work it out give the same answer, which is where these readers went wrong in practice rather than in the arithmetic.
  • Twelve properties over phylogenies and maps, the two subsystems the suite had never covered, and each one checked by breaking the library on purpose to confirm it notices. Trees: every node placed exactly once with the tips filling the rows, a parent between its children, the rows identical whether depth is measured or counted, a cladogram depth equal to the number of branches, and a parser that decides rather than panics on a file that is not a tree. Rerooting is held to the only thing it must not do, by an oracle that walks the undirected edges itself: every tip-to-tip distance survives rooting on an internal node, on an outgroup and at the midpoint, and a refused rerooting leaves the tree untouched. Maps: a valid document, the same document twice, no location dropped without the notice saying how many, coordinates kept rather than clamped, and nothing projected off the page. The last of those covers path data as well as attributes, since that is where a projection actually lands.
  • Row-major Panels grids for balanced comparison sheets, plus quieter clade fields, halo-separated node compositions, compact annotation legend chips, aligned-tree dividers and washed domain architectures.
  • Node-attached bubbles, pies, donuts and stacked bars from exact numeric annotations; clade highlights that become rectangular bands, radial sectors or unrooted fields; tree-aligned MSA rows; and a new DomainTrack for domain, motif, exon or repeat architectures ordered by phylogenetic descent.
  • Crossing-aware tanglegram untangling, equal-angle unrooted trees and layered iTOL-style annotation strips, bars, binary markers and symbols.
  • Projection-independent branch evidence for phylograms: support can use scaled symbols, exact labels and a threshold in either common convention; branch-specific annotations can follow their edge without ancestral inheritance; and automatic or exact scale bars retain evolutionary units in rectangular, circular and unrooted coordinates.
  • Validated rerooting around an internal node, a strictly monophyletic outgroup or the weighted midpoint. Tip distances and edge support are preserved, invalid requests do not mutate the tree, and rooted projections can mark the selected root explicitly without leaking that marker into unrooted views.
  • A visual plot catalogue for the documentation, separating discovery from the exhaustive track reference. Eight category pages group all thirty-three tracks and the three standalone drawings by biological question, data shape and coordinate system, with responsive image-led cards, shorter section-only tables of contents and direct routes to the exact API contract.
  • Every figure now says what it is. The document carries a <title>, a <desc>, role="img" and aria-labelledby, so a screen reader is given a sentence instead of several thousand unnamed rectangles, and a reader whose image did not load is given the alt text. Figure::description sets that text; without it the description is composed only from what the figure knows for certain, the region and the labels of its tracks in order, which says what is there without claiming anything about what it shows.
  • SvgWriter::begin_titled and SvgWriter::describe, and a tooltip on every glyph worth pointing at: a gene gives its name, span and strand, a variant its position, consequence and allele fraction, a read its span and CIGAR. A <title> as the first child of a group is SVG 1.1, so this needs no script, fetches nothing, and survives Inkscape and Illustrator.
  • What gets named follows the rule the crate already binned by: the thing that decides whether a datum is drawn on its own decides whether it is named. A coverage bin is the resolution of the output rather than of the input and gets nothing; a dense row-major track names the row and not the cell; a glyph under a pixel is nothing a pointer can rest on. The worst a figure grew was a third, and the genome-wide figure did not grow at all.
  • SvgWriter::begin_titled_inert for decoration drawn over data. The ideogram's region marker is a translucent rectangle written after the bands, so its own tooltip took every hover inside the highlighted span and the bands it points at were the one part of the chromosome a reader could not interrogate.
  • plot and Plot, a short way to write a figure down. Figure takes tracks that already exist, which is right when a track is built elsewhere and wrong for the ordinary case: a handful of tracks written out once, in place. There the plumbing was most of the code. A Region to unwrap, a use line naming every track type, a push around each one, an AxisTrack to remember, and the window start repeated on every track that takes one even though the figure already knew it. plot("NC_000962.3:761,000-763,000")? starts a stack and one add_ call per track fills it, in the order they stack.
  • Plot holds the track added last in its type parameter, so Plot::label names it and Plot::adjust hands it to a closure that sees the concrete type. Every builder method on the track is therefore in reach, and a name that does not exist on it is a compile error rather than a call that goes nowhere. This is the reason the type has a parameter at all.
  • Two things a plot fills in, both of which can be undone. An AxisTrack goes on the bottom, since a figure without coordinates along it is rarely what anyone meant; Plot::add_axis puts it elsewhere and Plot::remove_axis leaves it out. Tracks carrying an array rather than coordinates start at the left edge of the region, with an _at form for the ones that do not.
  • Plot::done, which puts the pending track away without adding another. A stack built in a loop or behind a condition needs every arm to have one type, and the type parameter that makes adjust work is what takes that away.
  • Plot::add_track and Plot::add_boxed, for a track that is already built: one an alternative constructor made, one read back before it is drawn, or one from outside the crate. This is not a fallback but the ordinary way to write a heavily configured track, since the builder chain says it in one line and adjust would wrap the same calls in a closure.
  • A command line front end, karyon, in its own binary target. The library still reads no files and still has no dependencies: everything the command reads is line based text, and the reading lives in src/bin/karyon/. The grammar is the facade with spaces instead of dots, because argv is already an ordered list whose later words describe the earlier ones: each --<track> flag starts a track and the flags after it describe that one, so the order of the flags is the order of the stack.
  • Twelve tracks have a standard text format to read and are the ones the command has. BED, bedGraph, cytoBand, GFF3, VCF, SAM, FASTA, Newick and two tables. BAM, CRAM and BCF are not read at all and are not meant to be: they come in through a pipe, since samtools depth, samtools view and bcftools view already write what these readers take.
  • Coordinates are read as each format defines them, which is the one thing in the command that would fail silently. BED, bedGraph and cytoBand are 0-based half-open; GFF3, VCF, SAM and samtools depth are 1-based. Every reader has a test that pins a known base through the conversion, and the end coordinate is pinned separately from the start.
  • Tree, Clade and Placement at the crate root. TreeTrack, CladeTrack and TanglegramTrack were re-exported there and all three take a Tree, which was not.
  • Typed phylogenetic annotations through parse_annotated_newick and parse_nexus, including BEAST, NHX, rootedness markers and Nexus translation tables. Tree now exposes MRCA, ancestor and descendant queries, rotation, ladderising, internal-node rerooting, subtree extraction and collapse.
  • Dated TreeTrack layouts, metadata-driven branch colours, visible internal nodes, non-destructive clade triangles and categorical or continuous trait columns aligned to the terminal taxa. The synthetic surveillance example is a committed visual regression target for the complete composition.
  • Circular TreeTrack projections with complete or partial sweeps, controllable start angle and central gap, outward or inward branch direction, concentric time guides, annular categorical or continuous trait rings and non-destructive collapsed-clade wedges. The four-view radial gallery is a committed visual regression target.
  • Map, GeoLocation, GeoFlow and three explicit geographic projections. Locations carry exact coordinates, counts and categories; categories use shape as well as colour, weighted links retain direction, and invalid, ambiguous or hidden inputs are counted rather than clamped or dropped. The Natural Earth 1:110m land geometry is public-domain data compiled into the crate, so rendering stays offline and deterministic.
  • PhyloMap and PhyloConnector, composing an inward circular time tree, phylogram or cladogram around a central map. Terminal annotations match a unique supplied coordinate table, with aggregated, individual or absent connectors; the renderer does not infer coordinates, ancestral states or migration. A two-view circular phylogeography gallery pins the composition as a visual regression target.
  • From<Error> for std::io::Error. A program that draws figures spends its errors on writing files, so its functions return io::Result, and the one call that parses a region was the only thing in such a function that could not use ?. That is a poor reason to reach for unwrap, and it was the second line of all fourteen chains in the examples.
  • Empty, Slot and Named, the plumbing that gives Plot its type parameter, under karyon::plot rather than at the crate root. Empty is deliberately not a Track, so label and adjust before any track has been added are compile errors rather than calls that go nowhere, and a figure cannot be asked to draw the empty slot. Slot and Named are sealed.
  • CodonTrack, a ruler in codons. A variant in a coding sequence is named by residue (BRAF V600E, TP53 R175H, rpoB S450L) and the crate could only speak bases, so no figure could be pointed at with the name its result has. It partitions a coding sequence into codons, numbers them, and translates them where a letter fits. The partition is itself the claim: two changes in one codon are competing alleles at one residue, not a double mutant. On the reverse strand codon 1 sits at the highest coordinate, which is the whole reason this is a track and not a division by three: roughly half the coding sequences in any annotation run backwards, and getting their numbering wrong is silent.
  • CodonTrack::genetic_code, since the translation table was hard-coded to NCBI table 1. Table 11 gives the same residues so bacteria, archaea and plastids were fine, but a mitochondrial or ciliate sequence was quietly translated into a plausible protein that was wrong.
  • SplitReadTrack, SplitRead and SplitSegment, reads that align in pieces. A PileupTrack read is one start, one CIGAR and one strand, so a molecule that visits three places cannot be written down in it, and a StructuralTrack arc starts from a finished call, so by the time there is an arc the evidence has been summarised away. This is the evidence: one row per molecule, one bar per alignment, connectors saying in what order and orientation it went. A backward hop dips under the row and a forward one arches over, so a read crossing an inversion is a different picture from one crossing a deletion. A transposition is three segments and not an arc.
  • CladeTrack and CladeBlock, genomic intervals painted onto a phylogeny. A MatrixTrack cell is one base wide and cells never merge, so a matrix can only say that six samples each carry something here, which is six observations. A block whose height covers a clade says one. The track can lie in exactly one way and refuses to: rows inside a block that do not carry it are cut out, so a paraphyletic set can never pass for a clade.
  • TranscriptionUnitTrack, TranscriptionUnit and Terminator. Nothing in the crate touched transcription. The span from the bent arrow to the hairpin is one RNA molecule, so the genes under it are co-transcribed and a promoter mutation upstream changes all of them at once. The 5' leader is drawn hollow, which makes its length a distance to measure rather than a number in a caption, and a leaderless transcript is a different picture rather than a different label.
  • examples/reading.rs and four assets, plus gallery panels S to V. The sheet is twenty-two panels.
  • The README track table now lists all thirty-three track types. It had stopped at AxisTrack.

Added

  • tests/properties.rs, which asserts invariants rather than examples. A test written beside the code inherits the blind spot of whoever wrote it, because the person writing the assertion is the person who believed the code: a reverse-strand span was wrong here from the first commit and every test of it agreed with it. An invariant can be checked without knowing the right answer, which is the only way to find something nobody thought of. Seven of them so far: every generated figure renders a valid document, rendering is deterministic, the reported dimensions are the ones in the document, a locus string survives a round trip, the scale never goes backwards, changing the theme changes no geometry, and a track added below never shortens the figure.
  • The figures are generated by a seeded LCG rather than by a property testing crate, so the dependency count stays at zero even for the tests, and a failure is reproducible from the seed in the message. What it feeds in is the point: not plausible depths but NaN, both infinities, f64::MAX, u64::MAX, empty vectors, one-base regions and names carrying markup. Ten thousand figures take about three seconds.

Fixed

  • A variant tooltip printed f64::MAX as three hundred and ten digits. The guard against that existed and its fallback expanded the number anyway, and its threshold was above the point where the precision it was protecting had already gone: an f64 holds consecutive integers to 2^53 and therefore consecutive hundredths only to a hundredth of that, so 999,999,999,999,999 came back reading .04. Past that the magnitude is all there is to say, and a new invariant says no tooltip may carry more than twenty digits in a row, which is what u64::MAX needs and more than any grouped number reaches.
  • Six coordinate overflows, every one found by the property suite on its first run and none reachable with ordinary data: Scale::bounds, Read::end and the CIGAR walk, the codon walk in OrfTrack, and the sequence track's right edge. They are the same defect in six places. A coordinate near the top of the range does not survive a trip through f64, since u64::MAX - 10 is not representable, rounds to 2^64 and comes back as u64::MAX, so anything added to it overflows. All of them saturate now, which is what the arithmetic in the rest of the crate already did: a panic is what happens in a debug build and a wrapped coordinate is what happens in a release one, and the wrapped coordinate is the worse of the two because it draws.

Thirty-five defects, found by hunting the crate along seven failure modes and then handing each claim to an independent reviewer whose job was to refute it. Seven were refuted. Every one below has a reproduction that runs and a test that pins the answer.

Wrong figures.

  • Reverse-frame open reading frames were a whole codon out at both ends. They swallowed the stop codon that is supposed to bound them and dropped their last codon, so OrfTrack reported 94 codons where there were 93 and painted the bar over the very tick that ends it, while the three forward lanes beside it were right. Frame -k of a sequence walks the codons frame +k of its reverse complement walks, and over 5,520 random sequences 5,519 disagreed with their own mirror. They now all agree, and the property is a test.
  • MatrixTrack put its cell half a floor-width to the right of the site it stands for, so it did not line up with the Manhattan point above it in the same figure.
  • contrast_ink chose the less legible ink for adenine and guanine. The contrast ratios say so, and now the ink follows them.
  • AxisRing placed its labels from the outer radius alone, so a thin ruler printed its coordinates on top of the ring inside it.
  • Every track was clipped to the widest value axis in the figure, so a track that asked for no axis could paint into the strip another track had reserved.
  • A track label longer than the gutter was drawn off the left edge of the canvas and lost its first characters; it is ellipsised now.
  • Panels sized the sheet from the panel widths alone, so a caption could be painted over the next column.
  • Panels::share_out did not return the most level partition among those tying on the tallest column.
  • FeatureTrack packed features that were off screen, so the band height and the row of every visible feature depended on data nobody could see.
  • AxisTrack's tick loop overflowed at the top of the coordinate range: a panic in a debug build, and 503 wrapped-around tick labels in a release one.
  • A doubled quote inside a quoted Newick label started a new taxon, inventing tips that are not in the tree.
  • Genome::at did not bound-check, so a coordinate past the end of one sequence was drawn inside the next one and counted as mapped. It now accepts exactly the positions Genome::locate inverts, which is a narrowing: the end of a half-open interval is Genome::offset, not at.

Data dropped without a word.

  • CodonTrack drew nothing at all when the coding sequence length was not a multiple of three, or when the trailing partial codon was in view.
  • A structural variant whose two breakpoints straddle the whole window was dropped, so the largest events were the ones that vanished.
  • Tree::parse_newick silently discarded every unnamed leaf, so a legal Newick tree lost tips and changed topology.
  • A UTF-8 byte order mark made the first data row of every column reader disappear.
  • Track heights were computed against Theme::default while the rows were packed with the figure's own theme, so any other font size could cut a row off the bottom of a band.
  • Rings::link collapsed the origin-spanning end of a chord to a hairline.
  • --matrix whose sites all fall outside the region was a silently empty lane and exit 0.

Output that was not valid SVG.

  • --color was pasted into an attribute unescaped. That is attribute injection, not a malformed file: a value carrying < put elements of the caller's choosing into the document. Metacharacters are refused now.
  • svg::num wrote the literal inf for finite inputs above f64::MAX / 1000, which was the one guard the writer had against a non-finite number reaching the page.
  • escape let XML-forbidden control characters through, so a name read from a user's file could produce a document no parser accepts.
  • A theme font size below 1 made tracks emit a negative font-size.
  • A non-finite figure height was written as height="0", and on a sheet it stacked every later panel on top of it.

Panics on data.

  • u64 coordinate arithmetic overflowed across eight constructors.
  • A large finite Figure::width panicked with a capacity overflow.
  • A named origin-spanning feature panicked FeatureRing with a subtraction overflow.
  • A number on the command line unwound instead of returning an error.

And the gate that should have caught some of this. The CI step that fails when the committed figures disagree with the code rendered ten of the sixteen examples, so six figures could drift unnoticed, and it never checked the copies the documentation site serves. It renders all sixteen now and checks both.

  • A nested figure named itself, which left every panel of a sheet unnameable. <title> resolves to the innermost element under the pointer, so the title a figure writes for itself shadowed the one Panels puts on the panel over the panel's whole area: all twenty-two panel tooltips in the gallery existed in the file and none could be reached. A document rendered with an id prefix is being nested, so it no longer names itself; role="img" inside another role="img" also hid its contents from a screen reader rather than describing them.
  • SnpTrack hung its tooltip on the rotated column label rather than on the data, so the panel itself answered nothing, and show_positions(false) took every tooltip in the track with it. The cell is what a pointer lands on, so the cell now carries the site, the reference base and what that sample has. Only a cell that differs is named: agreement is the state the panel exists to see past, the colour already says it, and naming it cost a third of the file.
  • MatrixTrack counted every site in the track while the drawing loop skipped the ones outside the region, so a zoomed figure described cells that were not on the page. Both numbers now count over the predicate the drawing uses.
  • Four tracks spelled the coordinate conversion themselves as start + 1, which dropped the guard on a degenerate interval, so a zero-length span came out backwards: a clade block at 100 to 100 read 101 to 100. There is one span formatter in the crate now and all of them call it.

Changed

  • Every module doc is at one level. The crate had been written by many hands and read like two crates: eighteen modules carried a single line of //!, among them figure.rs at 462 lines of code, theme.rs, codon.rs and all four command line readers, while bisulfite.rs carried three named sections and the crate root carried eight. A module doc now opens with one summary line and an orienting paragraph, and takes named sections in twos or more, since exactly one heading is worse than none. Headings state a claim or ask a question, the way "The frames are not the strands" does, rather than saying Overview or Usage. The spread went from 1 to 106 lines to 17 to 105, with the middle half of the crate between 28 and 35.
  • A module doc states the decision and the item doc keeps the reasoning, which is a rule the levelling pass broke before it held it: seven modules ended up restating their own /// docs, and in two of them the same sentence stood in three places counting the documentation site. Those are back to one place each.
  • The documentation site is one shape too. Every page title is a noun phrase, which matters because the title is the navigation label, and thirteen of the fifteen pages now close with the ## Next block the other tools in the lab use. The two that do not are the home page, which has the card grid, and the changelog, which is an include.
  • The examples are no longer all one organism, and three of them were showing an assay the organism does not have. The bisulfite panel drew a CpG island on a genome whose methylome is 6mA and which has no 5mC at all; it is now the human H19/IGF2 imprinting control region, which is where a per-molecule bisulfite plot with two populations comes from. The per-strand methylation panel claimed Dam and GATC in a genome with no dam; it is now E. coli K-12 at oriC, with the eleven real GATC sites SeqA holds hemimethylated. The pangenome matrix drew an accessory genome with gained and lost islands in a species that has neither, and is now Klebsiella pneumoniae. The clade panel needed a block that was lost more than once, which a clonal organism does not supply, so it is SARS-CoV-2 lineage-defining deletions with the nsp6 SGF deletion as the recurrent one.
  • Coordinates in the remaining figures now match the annotation they name. rpoB was drawn 1,050 bases long instead of 3,519 with an invented neighbouring gene beside it; the RRDR box sat on codons 525 to 572 rather than 426 to 452, contradicting the frame CodonTrack pins in its own doctest; RD1 was drawn at 8 kb, on top of gyrA and gyrB, rather than at 4.35 Mb; eccCa1 was drawn on the reverse strand when its locus tag has no c suffix. The split-read panel asserted an empty donor site and twice the depth over that same donor, which no single sample can show, and put the element in two orientations for one insertion event.
  • Documentation throughout now states the general case rather than the bacterial or tuberculosis one. Twenty-three sentences across the API docs and the README said what the library is for in terms of one clade: circular plotting existed because "a bacterial chromosome has no ends", protein coordinates because of what is "clinically interesting about a bacterial genome", split reads for IS6110. The crate is organism-agnostic and the prose now reads that way. Example and doctest data are untouched, since a figure has to be of something and rpoB at H37Rv coordinates is as good as anything.

Removed

  • AccumulationTrack, AccumulationCurve, DistanceTrack, FrequencyTrack and Frequency, with their examples, assets and gallery panels. All three tracks failed the only entry test this crate has: none of their draw methods ever reads ctx.scale. Their x axis was a count of genomes, a count of genomes and a list of sample names, so what they actually drew was a bar chart, a line chart with a quantile ribbon and a clustered heatmap. A general plotting library draws those better, and src/track/distance.rs had said so in its own header since the day it landed: "Nothing genomic. Both axes of this track are the sample list, so it does not use the figure's shared coordinate system."
  • Breaking, as the names were public re-exports. The analyses they carried are worth keeping somewhere, but a rarefaction over a presence matrix is a statistic, not a plot type, and it does not need a Track to compute it.

Fixed

  • MethylationTrack::hemimethylated only paired the two strands when both calls sat on the same coordinate, which is never true of a palindrome: the two 6mA of a GATC are one base apart, and so are the two 5mC of a CpG. The API forced the caller to lie about one of the two positions, and the E. coli oriC panel found it the moment its coordinates became real. It now pairs the nearest partner within MethylationTrack::pair_within, one base by default.
  • Panels::columns now cuts the panels where the tallest column comes out shortest, and levels the rest, instead of filling each column until it has passed its share. The old rule compounded: one column overshooting left every column after it short, and taking three panels off the gallery sheet turned a 159 pixel difference between the columns into 317. The cuts are now chosen by weighing all of them, which for a sheet of eighteen panels is nothing, and the same sheet comes out 66 pixels shorter with a 111 pixel difference.

0.14.0 - 2026-07-31

Added

  • Panels, several figures in one document, which is what a multi-panel paper figure is and what an overview of a whole library needs. Each figure is embedded as a nested <svg> inside a translated group, so nothing is reparsed and nothing is rewritten: a panel on a sheet is the same picture as the panel on its own, and there is a test that asserts exactly that.
  • assets/gallery.svg and examples/gallery.rs, one panel per kind of plot. When a new track type is added it gets a panel here, because an overview that quietly stops covering everything is worse than no overview: it looks complete.
  • MatrixTrack::tree, the same phylogeny ordering SnpTrack already had. For a pangenome it is the difference between a figure and a shrug: sorted by sample name an accessory region is a speckle, sorted by descent it is a rectangle, and a rectangle is a claim about the biology.
  • assets/example-pangenome.svg, a presence and absence matrix ordered by its tree.
  • track::tree::leaf_order, the row permutation both tracks now share, so they cannot drift apart on how an unnamed row is handled.

Fixed

  • MsaTrack::consensus broke ties by the last residue seen rather than the first, contradicting its own documentation. It used max_by_key, which returns the last maximum. Two residues tied in a column therefore picked the wrong one, though the result was at least stable between runs.
  • Six clippy lints that had been failing CI: three functions over the argument limit, two byte-string literals and a single-character push_str.

Changed

  • The CI workflow runs on workflow_dispatch only. Pushing no longer spends Actions minutes; run it from the Actions tab or with gh workflow run ci.yml --repo PathoGenOmics-Lab/karyon --ref main.

0.13.0 - 2026-07-31

Phylogenies, and the reason to have them here rather than in a tree viewer.

Added

  • karyon::tree, with Tree::parse_newick reading nested clades, branch lengths, quoted names, scientific notation and internal labels, taking an internal label as a support value when it parses as a number and as a name when it does not. Nodes live in one flat list and refer to each other by index, so a ten thousand deep ladder lays out without touching the stack.
  • Tree::layout, placing every node at a depth and a row, as a phylogram from the branch lengths or as a cladogram counting branches instead.
  • TreeTrack, a phylogeny as a track of its own, with TreeShape choosing between the two.
  • SnpTrack::tree, which is the pairing the tree is here for. It draws the phylogeny in the strip beside the panel and sorts the rows by descent, so a clade's shared substitutions line up into a block instead of scattering down the panel in sample-name order. Rows match leaves by name, and a sample the tree does not mention keeps its place at the bottom rather than vanishing: a row silently dropped from a figure is worse than a row out of order.
  • Error::InvalidNewick, so a malformed tree is rejected with a reason rather than guessed at.

Notes

  • Branches are drawn rectangular rather than as diagonals. A diagonal would imply the tree says something about the space between two rows, and it does not.

0.12.0 - 2026-07-31

Added

  • SnpTrack and SnpSite, a panel of the columns that vary and nothing else. An alignment of closely related genomes is almost entirely agreement: thirty kilobases carrying thirty-four differences would spend 99.9% of a plot on the part that says nothing. SnpTrack::from_alignment finds the variable columns against a reference row and drops the rest.

The idea is the one snipit is built around. The implementation and the drawing are this crate's own.

  • SvgWriter::text_rotated, since a five digit position has to stand on end to fit under a column narrower than it is.

Notes

  • The x axis of a SnpTrack is not linear in the genome. Two adjacent columns may be nine bases apart or nine kilobases apart, and the spacing says nothing about which. Every column therefore carries its own position, and an AxisTrack does not belong underneath one: a ruler there would be a lie.
  • A cell matching the reference is drawn as a quiet bar rather than a letter, on the same reasoning as the alignment track: the matches are the noise.

0.11.0 - 2026-07-31

Added

  • MsaTrack and MsaSequence, a multiple sequence alignment drawn row by row. The coordinates are alignment columns rather than genomic positions, which are two different things, so the figure's region is the column space and the ruler counts columns. Ungapping a row back to reference coordinates is a real operation with real decisions in it and is not done silently.
  • MsaDisplay, defaulting to Differences. In a real alignment most cells agree and the agreement is the noise, so rows are drawn as a quiet bar and only what disagrees with the comparison row is painted. MsaTrack::compare_to names a reference row; without one the consensus is used.
  • MsaTrack::consensus, whose ties break by which row came first so that the same alignment always gives the same answer.
  • MsaColoring and ResidueClass, six physicochemical classes for proteins, six being how many hues the validated palette has. Cysteine goes with the hydrophobics, histidine with the positives, tyrosine with the polars, and glycine and proline keep their own.
  • MsaTrack::max_rows, with the sequences that do not fit counted and reported on the figure rather than dropped quietly.
  • Two more example figures, one nucleotide alignment under a conservation logo built from the same sequences, and one protein alignment by residue class.

Notes

  • Neighbouring cells of the same colour are merged into one rectangle. Twelve rows of a hundred and twenty columns come out as a hundred and twelve rectangles rather than one thousand four hundred and forty, which is the difference between a figure and a file no viewer will open.

0.10.0 - 2026-07-31

The last of the comparison plots on the roadmap: one set of alignment blocks, drawn two ways.

Added

  • AlignmentBlock, one alignment between a stretch of the query and a stretch of the target. Both spans ascend and the strand is a flag, which is how PAF records it and saves a conversion on the way in.
  • DotplotTrack, which gives the target the vertical axis. Forward blocks climb, reversed ones descend, and a translocation sits off the main diagonal, so a rearrangement has a shape rather than a description.
  • SyntenyTrack, which gives the target a second bar and joins the two with ribbons. An inversion becomes a twist. Ribbons are translucent so two crossing ones read as two, and every block is also drawn solid on both bars, so a thin ribbon still shows exactly what it connects.
  • Both take the whole target sequence with target_length or a window of it with target_range, and infer the span from the blocks when given neither.
  • Two more example figures: two chromosomes disagreeing in three places, and the inversion close up.

0.9.0 - 2026-07-31

Two more kinds of plot, chosen because they answer consecutive questions.

Added

  • ManhattanTrack and Association, association statistics along the sequence with a threshold line and hits coloured and ringed above it. Association::from_p_value converts a p-value to -log10(p) and floors a zero at the smallest positive double rather than letting it become infinity. genome_wide_threshold is the conventional -log10(5e-8), documented as a Bonferroni correction for a million independent tests and therefore the wrong number for most organisms.
  • MatrixTrack, MatrixRow and CellScale, a sample by site matrix: a genotype matrix out of a VCF, or a pangenome presence matrix once its genes have coordinates. Row names go in the axis strip, so they size themselves. CellScale::Sequential for quantities and presence, CellScale::Categorical for genotypes, where the numbers name things rather than measure them.
  • assets/example-association.svg, a Manhattan tower with the gene under it and the isolates carrying the haplotype under that, all on one x axis.

Notes

  • A sequential matrix ramp starts a step off the page rather than on it. Three things have to look different in a genotype matrix: a sample that does not carry the allele, a sample that was never typed, and empty page. A pure surface at the bottom of the ramp collapses the first two into the third.
  • MatrixTrack gives every cell a minimum width, since variant sites are points and would otherwise be sub-pixel slivers. A cell's width therefore says nothing about how much sequence it covers.

0.8.1 - 2026-07-31

Added

  • A value axis on VariantTrack, so an allele frequency can be read off the plot rather than guessed from the height of a stem. VariantTrack::show_scale turns it off.

It appears only when it would mean something. VariantStyle::Tick draws every mark at full height by design, so it gets no axis; and a track whose variants carry no values has no ceiling to label, only a convention that a valueless stem is drawn full height. Putting a "1" on that would be inventing a measurement. A pinned max counts as a ceiling even with no values.

Frequencies keep their decimals, since a scale that rounded 0.64 to 1 would be worse than no scale, while large values still shorten to 1.5k and 2M.

0.8.0 - 2026-07-31

Quantitative tracks get a real value axis. Until now a coverage track showed only its maximum, crammed into the top left corner on a translucent patch, and a reader could not get a number off the plot at any other height.

Added

  • Track::y_axis_width, which a track implements to ask for a strip between the labels and the plotting area, and DrawContext::axis, the rectangle it gets. The figure reserves the widest request across every track, so the plotting areas still line up and the shared x axis survives. A track that wants no axis returns zero, gets a zero-width strip, and cannot draw outside itself: the clip covers the band and the strip together.
  • A value axis on CoverageTrack: the ceiling and zero, labelled outside the plot, with a hairline across it at the top of the scale. Two ticks rather than a ladder, because a coverage profile is read for its shape and its order of magnitude and six gridlines would be more ink than the thing they measure.
  • The same for LogoTrack, whose top and bottom values move out of the band.

Changed

  • Track labels sit to the left of the value axes, so a track with an axis and one without still line their names up.

0.7.0 - 2026-07-31

Added

  • IdeogramTrack, a chromosome drawn end to end with a marker showing which part of it the rest of the figure is showing. It is the one track that does not map its data through the shared Scale, because a track that only showed the region on display could not say where that region is.
  • Band and Stain, with Stain::from_name reading the gieStain column of the UCSC cytoBand table so a row of that file converts without a lookup table of your own. An unrecognised stain becomes the palest shade rather than an invented dark one.
  • IdeogramTrack::bare for a chromosome with no banding, which is what a bacterial genome wants, and IdeogramTrack::highlight for marking a span other than the one on display.
  • theme::mix, which blends two colours. The cytogenetic grey ladder is mixed from the theme's own ink and page, so a dark figure gets a dark ladder rather than a light one left uninverted.
  • SvgWriter::rect_outline, SvgWriter::path_stroked and SvgWriter::begin_clip_path, the last so bands can be painted inside an arm's silhouette and keep its rounded end.
  • Two more example figures: a banded chromosome above its detail tracks, and the H37Rv chromosome as a bare outline with rpoB marked on it.

0.6.0 - 2026-07-31

A pass over how the figures look, with the colour half decided by measurement rather than taste.

Fixed

  • The categorical palette had two slots, a brown and a grey, that were 1.8 apart under protanopia and 7.8 apart even with full colour vision. Any figure with seven or eight categories was drawing two of them the same. The palette is now six hues, every pair of which clears the separation floor.
  • The dark palette failed on every count: half of it sat outside the lightness band a dark surface needs, four entries were desaturated enough to read as grey, and two were 10.4 apart. It has been replaced with steps chosen against the dark surface and validated there.

Added

  • BaseColors::colorblind_safe, four nucleotide colours whose closest pair is 11.0 apart under deuteranopia. The conventional IGV colours remain the default and are now named BaseColors::conventional, with their measured weakness stated: adenine and guanine are 1.7 apart under protanopia, which is the transition pair.
  • Theme::corner_radius and Theme::insertion, the latter so a pileup's insertion marks stop being a hardcoded hex.
  • SvgWriter::rect_rounded and SvgWriter::circle_ringed.
  • assets/example-dark.svg, because a theme nobody has looked at is a theme nobody has checked.

Changed

  • A coverage area is now a wash under a drawn line rather than a saturated block: the line carries the shape and the fill only says which side of it is under the curve. Bars stay solid, since there the bar is the mark.
  • Coverage fits its axis with a little headroom, so the tallest point reads as a peak instead of something that ran out of band. A pinned maximum is still taken literally.
  • Variant markers are large enough to see and carry a ring in the page colour, so two variants a base apart read as two variants rather than one blob.
  • Features and reads have rounded ends.
  • Lines are 2 pixels rather than 1.2.

0.5.0 - 2026-07-31

Added

  • PileupTrack, aligned reads stacked into rows the way a genome browser stacks them, with mismatches painted against the reference.
  • CigarOp and Read, which model an alignment rather than an interval. Every SAM operation has somewhere to go and consumes what the specification says it consumes, so converting a BAM record is one arm per letter. M, = and X all become Match: the track compares the sequences itself rather than trusting the operation.
  • Read::segments, which walks the CIGAR and places every piece on the reference, and Read::base_at, which finds the read base at a reference position through whatever insertions and deletions lie in between.
  • PileupTrack::max_rows, defaulting to forty, with the reads that do not fit counted and reported on the figure instead of dropped quietly. PileupTrack::layout returns that count.
  • ReadColoring::Strand for strand bias at a glance, and PileupTrack::fade_by_quality, because a read with mapping quality zero could have come from anywhere and should not be drawn as solidly as one that could not.
  • A fifth example figure, assets/example-pileup.svg, whose depth profile is computed from the same reads the pileup draws.

0.4.0 - 2026-07-31

Empirical Bayes stabilisation, the other half of the Logolas paper. A logo can now tell four sequences from four thousand.

Added

  • karyon::dash, an implementation of Dirichlet adaptive shrinkage. Each column of counts is modelled as multinomial with a prior that is a mixture of Dirichlet distributions, all centred on a background and differing in concentration. The mixture weights are fitted by EM across every column at once, so a well sampled column overrules the prior while a thin one is pulled towards the background. From Dey, Xie and Stephens, BMC Bioinformatics 19:473 (2018).
  • LogoTrack::stabilize and LogoTrack::stabilize_with to apply it, and LogoTrack::sample_size for the case where the input is a probability matrix and carries no record of how many sequences it came from.
  • LogoTrack::dash_fit, returning the fitted mixture weights and, through DashFit::shrinkage, how far each column actually moved. A figure that quietly moved the numbers should be able to say by how much.
  • karyon::dash::ln_gamma, since the standard library has none and this crate has no dependencies.
  • A fourth example figure, assets/example-logo-stability.svg, showing one set of proportions at three sample sizes drawn raw and shrunk.

Changed

  • LogoTrack::smoothing is not applied to a stabilised track. The shrunk composition is already strictly positive, so the smoothing would be a second repair on top of a first.

0.3.0 - 2026-07-31

Sequence logos gain the rest of the scoring schemes Logolas offers, and the score is separated from where the baseline sits.

Added

  • Four more scores on top of the existing three: LogoScore::KullbackLeibler (p log2(p/q), the symbol's contribution to the divergence), LogoScore::Difference (p - q), LogoScore::Ratio (p / q) and LogoScore::OddsRatio. Together with Probability, InformationContent and LogOdds these cover the Logolas set.
  • Centering, which decides where the baseline of a background-relative logo sits, as a quantile of the column rather than a fixed median. Quantile(0.5) is the default and matches the quant argument of Logolas; Centering::None leaves the baseline at "exactly the background".
  • LogoTrack::edlogo, shorthand for log odds centred on the median.
  • LogoTrack::max_extent, pinning how far the tallest stack reaches so that two panels of a figure can be compared honestly.
  • LogoScore::uses_background and LogoScore::unit.
  • A third example figure, assets/example-logo-scores.svg, showing the same four columns under all five background-relative scores.

Changed

  • LogoScaling is now LogoScore, and LogoTrack::scaling is LogoTrack::score. The old EnrichmentDepletion variant was the pairing of one score with one centring, which is now spelled LogoTrack::edlogo() or .score(LogoScore::LogOdds).centering(Centering::median()).
  • Smoothing now applies to every score measured against a background rather than to the one that used to exist.

Fixed

  • An information content logo labelled its axis 2 bits while drawing it to the tallest column on screen, so the annotation and the picture disagreed. The two absolute scores now get the fixed axis their convention asks for: probabilities to one, information content to log2(K), the way WebLogo draws DNA from zero to two bits. Two figures of two motifs are comparable again.
  • A fitted axis now measures only the columns inside the region on display, instead of letting an off-screen column set the scale.
  • The scale annotation reports the value at the bottom of a two sided logo as well as the one at the top.

0.2.0 - 2026-07-31

Added

  • LogoTrack, a sequence logo over consecutive positions, with three scalings: Probability, InformationContent (the classic seqLogo plot in bits) and EnrichmentDepletion, which scores each symbol as log2(p / q) against a background, recentres the column on its median, and stacks enriched symbols above a baseline with depleted ones below it. That last one is the plot Logolas calls an EDLogo, and it is the only one of the three that can show a symbol which is absent rather than merely uncommon.
  • LogoColumn, whose symbols are arbitrary strings rather than single letters, so amino acid codes, codons and k-mers all plot.
  • LogoTrack::from_sequences to count an alignment, and LogoTrack::from_matrix to take a position weight matrix.
  • LogoTrack::background for a non-uniform background, alphabet_size for counting symbols that never appear, smoothing for how loudly absence speaks, symbol_color for per-symbol colours, and order for which end of the stack the tallest symbol goes.
  • SvgWriter::glyph, which stretches one glyph to an exact box using textLength, so the renderer does the fitting rather than this crate guessing font metrics.
  • AxisTrack::center_on_bases, putting each tick in the middle of its base instead of on its left edge. A ruler marks boundaries, but a logo column wants its number underneath it.
  • Theme::cap_height_ratio, the cap height of the font as a fraction of the font size, which is what turns a box height into a font size for a logo.

Changed

  • Theme gained a field, so any code building one with a struct literal rather than from Theme::light() will need updating.

0.1.0 - 2026-07-31

First release. Everything below is new.

Added

  • Figure, which stacks tracks over one shared coordinate axis, reserves a label gutter only when a track asks for one, clips each track to its own band, and computes the image height from its contents.
  • Region, 0-based half-open, with Region::parse for the 1-based inclusive locus strings used by samtools and IGV, and a Display that round-trips through it.
  • Scale, the shared mapping between base positions and pixels, with x, x_center, pos_at_x and the two resolution helpers.
  • CoverageTrack for per-base signal, in area, line or bar style, binned to one point per pixel column with max, mean or min, with an optional log scale and a pinnable maximum.
  • SequenceTrack for the reference bases, drawn as letters, as coloured blocks or as a zoom hint depending on how many pixels a base gets.
  • FeatureTrack for annotated intervals, with strand arrows, per-feature colours, and packing into as many rows as the current zoom needs so that neither features nor their labels overlap.
  • VariantTrack for point events, as lollipops scaled by value or as ticks, coloured by category in order of first appearance, with a legend.
  • AxisTrack, whose ticks land on round 1-based coordinates and whose labels share one unit across the whole ruler.
  • Theme, with light and dark presets, the Okabe-Ito categorical palette and IGV-style nucleotide colours, all of it public and replaceable.
  • Track, the trait behind all of the above, so a track type the crate does not ship can live outside it.
  • examples/locus.rs, which renders the two figures in the README from a fixed seed.