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, asembB-M30…. - A
.hicis drawn rather than refused with thehic2coolandcooler dumpcommands 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--ldgives 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 thegunzip -kthat gives it back. cooler's.cooland.mcoolare still answered with thecooler dumpthat 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::sequencesanswers for a.hicfrom its header. FeatureRingpaints a feature in its ownFeature::colorbefore the ring's colours and the strand's, the orderFeatureTracktakes 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 saidsource 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, andexamples.zipholds 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 themosdepth --by 100000that counts it in windows, which are drawn there:karyon reads.bamwas told what the first argument is. A track drawn over a place beside files that would be drawn across the genome is named asArgError::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--ldor--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
--shadeon 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,.faior.gzi, is refused before anything is read, naming the file it indexes, asArgError::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.bwwas answered with thebigWigToBedGraphcommand that writes bedGraph, which--pileupwould have refused next. A--formatgiven 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--dynseqis refused saying it needs one, asArgError::PlacelessBigWig, where it was told what the first argument is; after--coverageand--windowsit is drawn across the whole genome. Files::sequencesanswers 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::Bgzflives inread::bgzf, andread::bamstill names it.Bgzf::tellsays 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::fillandBgzf::lineread bytes and lines from whereverBgzf::seekwent.read::bam::Indexisread::index::Index, andread::bam::indexstill reads a.baialone.- The questions
Filesasks about a BAM (its depth and its reads over a window, its sequences, one read by its name) are answered fromFiles::seekableandFiles::besideunless a type answers them itself, soDiskandHeldshare one reader of a BAM where each had its own, and a type wrapping anotherFilespasses on two questions for every reader that does not read a file whole.Heldholds 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--colorschose made the clash, given to two values or the one the palette deals another, the line names the two values and the colour instead, aslineage: L1 and L2 are both #aa0000, so each is a shape as well, and leaves--colorsout of it.docs/assets/start/tree.svgand its dark copy change. --colorrefuses a value holding=, which is--colorswith a letter lost, and names--colors; taken as a paint,country=Peru:#e7298awent into the fill as written, which is no colour at all.- A name ending in
.png,.epsor 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 withpdftops -eps, an EMF with Inkscape and a.svgzwith gzip, since pdftoppm writes none of them.--pdfis answered with-o figure.pdf, and--png,--jpgand--dpiwith the tools that make an image from either. Plot::savewrites 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
-, aspipe 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.execarries its own C runtime (+crt-static), as the musl build carries its libc. Linked the default way it importedVCRUNTIME140.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.
.gitattributeschecks every text file out with LF line endings, sodocs/data/draw.sh, which stopped at itsset -ein 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.svgand 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::ExtraRegionis 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::spreadisTraits::strips, which says what it draws; to an R userspreadread 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
spreadchose shapes against the six colours the crate ships whatever theme came after, and an explicitTraitStyle::Stripwas 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 readS26 +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 ofexample-phylogenetics.svgandexample-phylo-layouts.svgchange. -
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.svgis 22 pixels shorter, and one panel ofexample-phylo-layouts.svgloses 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.
Moleculekeeps only the sites a molecule covered.Molecule::coveringbuilds one from those alone,call(site)reads one back,calls()walks them in order anduncover(site)leaves one out;callsis no longer a public field. Twenty thousand long reads over thirty thousand sites peaked at 707 MB and take 56, with the same figure.-
CoverageTrackkeeps 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_totalbreaks 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. Inexample-cluster.svgandexample-structural.svgthe 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 forPairTrack, 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
--backgroundon 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. -
--relativedraws 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.svgchanges. - 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::Ramphas athroughfield, 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 withmore 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,.nxsor.treesnamed 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 writes95.3/88, is support, the last drawn and each kept assupport_1,support_2and so on, where it was read as a name. Error::InvalidNewickhas anatfield, 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 saysno number or date. -
A tree's
time_unitis 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 yearread a calendar year as a quantity of years.example-phylogenetics.svggrows a line, and the time guides ofexample-phylo-layouts.svgandexample-phylo-map.svgmove 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,rerootandCladeHighlight::newtake aNodeRef, and an index or a name is one, so every call that compiled still does.-
TraitLevelhas asymbolfield, 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
--relativein the playground took the word after it out of the command too,--with-treeamong them: the page's list of flags that take no value lacked it, and--no-legendand--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,intervalwhere there is a ribbon andlog scalewhere the scale is one, in place oftrajectory · 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,ω > 1andp ≤ 0.05, where it wroteomega<1andp<=0.05under a title readingselection atlasover every figure of one. Its axis saysω 8andω 1. -
The committed figures
example-selection-atlas.svgandexample-evolutionary-surveillance.svgchange with these, and the page figure of an alignment gains a ruler of columns. -
Tree::reroot,reroot_outgroup,reroot_midpoint,rotateandcollapseare#[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::nounsays what a track is, and every track of the crate says it; one that does not isa 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-barandTreeTrack::show_scale_bar(false)leave it out, and--scale-baris 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.svgandexample-selection-atlas.svg. - What a value axis measures is written under the track's name,
-log10 punder a scan andAFunder the calls, where it was words after the top tick:10 -log10 pread as ten minus something, and nothing named the calls' axis at all.Track::axis_titlesays it,ManhattanTrackandVariantTracktake one, and the command line sets both. A unit written as a symbol, thexof50x, 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-8forgenome_wide_threshold,p = 1e-5for the newp_value_threshold(1e-5), and the value itself forthreshold, over the line, or under it where the line runs along the top of the band.threshold_labelsays something else, or nothing. - A call's tooltip names its value as its axis does,
AF 0.48, where it saidvalue 0.48, andvaluestill where the axis has no title. - A VCF call with no
AFhas 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 noAFat 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.svgandgallery.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,
L4andKenyathe 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--columnsnor an appended sample repaints anything. A tree coloured by a column starts where the column does.TraitColumn::first_colorsets it by hand.example-snps.svgchanges: 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::keyis what a track needs explained at a zoom,Figure::keygathers every track's at the zoom the figure draws at,BaseColors::legendis the key to the four colours, andLegend::andjoins 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--thresholdand a pileup with--max-rows, and is now fifty-seven.karyon help allkeeps every word. -
karyon --helpfits 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, whichkaryon help allstill does, andkaryonwith 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, andNAis a value a column of places can hold, North America or Namibia.example-phylogenetics.svgandexample-phylo-layouts.svgchange 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_inkhands 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_inandarch_insay what a given region draws;widestandarchanswer 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::denseisread::signal::spansand 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 isCoverageTrack'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_spanslays those down, andfrom_pairscalls it, so scattered points still arrive the way they did.CoverageTrack::atreads one value back out.read::signal::windowsandread::pointwere 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::hicreads Juicer's.hica window at a time, with no dependency:header_offor its sequences, their lengths and its resolutions,contactsfor the cells of the map of a window's sequence with itself at one resolution, asPairs of two bins and a raw count,resolution_forfor the finest that cuts a window intoBINSbins or fewer, andbedpefor the texthictk dump --joinprints. 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 thehictk convertcommands, through a.mcool, that write the same map as version 9; normalised counts are not read.--pairsdraws a.hic, and a.hicnamed 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, asBuildError::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 arcssays otherwise. Over a chromosome of 249 Mb simulated in a.hicof 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.TrackSpecgains the public fieldresolution, so aTrackSpecwritten out field by field needs it.--circulardraws the place, one whole sequence, as a circle, theRingsplot only Rust reached: each track a ring in the order written, the first outermost, inside a ruler, askaryon NC_000962.3 --circular genes.gff3 calls.vcf.gz sampleA.bedgraph. Annotation is aFeatureRingpainted a strand a colour, named where it holds at most 20 named features; a depth is aSignalRingcut into a thousand arcs with its--aggregateand 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 aMarkerRingcoloured 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-scaleputs every ring of depth on one reach. The circle is as long as a FASTA, a BAM, a VCF's##contigor a GFF3's##sequence-regionsays, 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,--styleand--shadeamong 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, incircle.svg.Ring::label, withlabelonAxisRing,FeatureRing,SignalRingandMarkerRing: 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_spanscuts a per-base signal into equal arcs with anAggregate,samtools depthover a chromosome being four million lines that were four million sectors, andSignalRing::baseline_at_medianreads it against its usual level. A plot that uses none of them draws byte for byte as it did.--codonsdraws 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, askaryon 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.tbior.csiwhere 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--sequencetranslated, and a trailing partial codon is left off with a note. The table is the one the CDS row names withtransl_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 asCodonRefusal::Splicedrather than numbered across them, CDS rows that overlap or meet out of frame, as NCBI writes a ribosomal slippage, asCodonRefusal::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 saysstart_range(end_rangeon the reverse strand), as NCBI writes a CDS at the edge of a contig, asCodonRefusal::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 asArgError::CodonsWithoutPlacebefore anything is read.--codonstakes--label, the gene's name by default, and--color; it is on the scales line ofkaryon --help,karyon help codonslists 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 residuesCodonTrack::genetic_codetakes, andNonefor a number NCBI retired; a test holds each to the codons NCBI's notes say it reassigns.read::interval::codingreads each transcript as it codes, as aCds: its exons and its CDS as written, a CDS from one end of a gene to the other included, whichread::interval::transcriptsfolds into one piece for drawing, and beside it the CDS rows apart, each aCdsRowwith 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 itsID=.read::interval::translation_tablereads thetransl_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 2lays every sequence a bedGraph, asamtools depthfile, a bigWig, a--windowsfile 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 asArgError::PlacelessBigWig, which now answers only--dynseq, whose scores are drawn as bases. A.bednamed on its own is told once read: mosdepth's.regions.bed.gzis drawn across the genome as the bedGraph it is, and one of features is refused for want of a place as it always was.--renamemakes two files' names for a sequence one, and names it so under the figure, and a--shadefinds 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 ordersort -k1,1leaves 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_numbersandread::bigwig::genomeread every sequence of a file, andInvocation::genome_wide, throughTrackSpec::genome_wideandKind::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 fromtumour.bedgraphandtumour.cns, whichexamples.zipholds too, indocs/assets/start/genome-copies.svgand its dark copy. CopyNumberTrack::across(&genome)lays a segmentation on aGenomeof 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, as5:12,000,001 to 13,500,000, rather than on the shared axis..cnsand.segnamed on their own are copy number, CNVkit's segments and the table IGV and GISTIC2 read, and still need--ploidy.read::bcfreads a BCF a window at a time, through the CSIbcftools indexwrites beside it, from anything that reads and seeks, and hands its records over as the VCF textbcftools viewprints for them, byte for byte, floats rounded as htslib rounds them, aGTunder VCF 4.4 prefixed as htslib prefixes it, and one a sample left out, as4underDP:GT, printed.before VCF 4.4 as bcftools 1.24 prints it:windowthe records over a window, through an index or by reading every record,wholeevery record,countedthe records on each sequence, from the counts a CSI keeps or by reading every record, andheader_ofthe header, with its sequences and its samples.Fieldssays how much of each record to write: its site alone, asbcftools view -Gprints it, which leaves its samples' bytes undecoded; its site and each sample'sGT; or every field. The command line reads a BCF as it is, named on its own or after--variants,--genotypesor--structural, and draws from it the figure the same calls draw as VCF:--variantsand--structuralread sites alone and--genotypesGTalone,--structuralevery record, as it reads a VCF, and a cohort's BCF named on its own says how many samples--genotypesdraws from the names its header gives. It was refused with thebcftools viewthat 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.csiin 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.csiolder 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 thebcftools index -fthat writes it again; an empty window says what the file holds from the counts the.csikeeps, a sequence the header names and no record is on counted as none, and from the file's own records where the.csiis 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--renamegives 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 asbcftools view -Obwrites it, uncompressed as-Oudoes, or bare, BCF 2.2 is read, as htslib reads it;Binary::ofnames a bare one by its magic, and one piped into standard input is refused asking for its name, or for the textbcftools viewwrites. Each is held tobcftools view1.24 on small files it wrote, kept insrc/read/fixtures/bcfwith the script that writes them again.- A file compressed with bgzip and indexed by
tabixorbcftools indexis read a window at a time through the.csior.tbibeside it, looked for as htslib looks, a.csifirst: 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,--manhattanover a place,--featuresof a BED or of a GFF3 that says it is one, and--methylationgiven--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;--genotypesover 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 reasondocs/guide/cli.mdgives. 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
tabixcommand 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,cpwithout-p,rsyncwithout-tor 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 byHeldor a playground page, the index beside its file by name, are read the same way. read::tabixreads the rows of a bgzipped text file over a window through its index:headis the header and the first row, checked against where the index says the rows begin,rowsthe rows over a window, stopping at the first past its end, andwindowthe two together, which every reader of text takes as it takes the whole file.read::interval::reachis how far the gene models over a window reach either side of it, andread::interval::parentlesswhether 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::bigbedandread::twobitread 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,--windowsand--dynseq, a bigBed by--features, and a 2bit by--sequence,--orfs,--with-sequenceand a pileup reading against the figure's reference. Over a window many bases to a pixel,--coveragereads a bigWig from the coarsest zoom level of which a pixel holds two bins, each bin painted with what--aggregatetakes 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, asBuildError::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 thegunzip -kthat 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 astwoBitToFawrites them. Each reader is held to the UCSC tool that undoes its file,bigWigToBedGraph,bigBedToBedandtwoBitToFa, on small files kent's own tools wrote, kept insrc/read/fixtureswith the commands.- A gene a bigBed names is a place a figure is drawn over, and a stretch a
--shadenames, 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::indexreads the three indexes htslib writes, BAI, TBI and CSI, into oneIndex: the tree of bins at the depth the file says, which for a CSI was 0 from samtools, 6 from bcftools and 8 fromtabix -C, andIndex::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, andIndex::binnedwhether 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 rowstabix1.24 prints.read::bgzf::is_bgzftells a file bgzip wrote, which an index can point into, from plain gzip, which has to be read from its start.read::bam::windowreads 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, thenreads.bai. A BAM with both is read through the one samtools reads, and a.csithat is not one is refused, as samtools refuses it.Files::seekable, a file's bytes to read out of order, andFiles::beside, the file beside it under an ending, as an index is, with its name and whether it was written before the file.DiskandHeldanswer both, and the playground's page passes them on.--genotypes FILEdraws 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--matrixdraws 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,GenotypeandGenotypeState, andPlot::add_genotypes, behind it.read::point::genotypesreadsGTfrom wherever it is among a VCF's keys, a call with any copy unknown is no call, andread::point::samplesreads the names on the#CHROMline without reading a row.--sampleafter--genotypestakes 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
--genotypesdraws its samples, a row each, unless the figure draws them already.--vcfand--samples, as other tools spell them, are answered with--genotypesand--sampletoo. cohort.vcf.gz, forty samples called across rpoB, in the example files and inexamples.zip, and a "Genotypes of many samples" page drawn from it,docs/assets/start/genotypes.svgand its dark copy. The playground has an example of its own.--colors COLUMN=VALUE:#rrggbb,...gives the values of a--traitscolumn 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-bypaints. 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 incountry=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--traitson 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,--colormapand the like are answered with--colors, and--tss,--operonsand--terminatorswith--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, soTranscriptionUnitTrackstays library only, and the annotation reference says what does reach the command line.- PDF:
-o figure.pdfwrites the figure as a one-page PDF, and every drawing,Figure,Panels,Rings,MapandPhyloMap, hasto_pdfandsave_pdfbesideto_svgandsave_svg, withPlot::to_pdffor a plot. The PDF is the SVG read back byPdf::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 andrsvg-convertgive 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::notesnames anything the page could not carry, as a character no base font has, and the command line prints it.Pdf::from_svgreads 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 anemin the notes rather than dropping it. The reference has a page on how the PDF is made. Figure::shadeandPlot::shadeshade a stretch of the axis down every band laid on it, aShadeeach, as a genome browser marks a region of interest;Shade::namewrites its name at the head of the column andShade::colorgives 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, andTrack::shows_shadesis 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, as120-180, on an alignment's columns or a table's weeks. A gene of--lociis 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.bedpoints at--features.--highlightwith a place, and--roi,--vhighlightand--axvspanas other tools spell it, are answered with--shade.MatrixTrack::maxpins the value a colour ramp ends at: the top of a sequential one, whichever of it andCellScale::Sequential'smaxcomes last, or the end of the gain of a diverging one, whose loss keeps its own reach.--maxtakes it after--matrixand--heatmap, where the key of--heatmap depths.tsv --relative --max 3ends at 3× rather than at the 2.19× the table reached, and a top at or under the centre is refused. After--pairsit is the ceiling, and wins over the r² of 1 linkage is read against, and after--windowsit 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.jschecks how the pages rewrite a command, in CI beside the tree viewer's scripts.Figure::same_scaleandPlot::same_scaledraw 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_overandDrawContext::extentare how a track takes part, andFigure::extentswithFigure::share_extentsshare a scale across the panels of a sheet.--same-scaleon the command line does the same, across every panel of a figure of several places, and--maxpins the top of a coverage, a recombination rate or a scan, for figures drawn apart.--ymaxand--autoscale, as other tools spell them, are answered with these two.Feature::exons,Feature::coding,Feature::transcriptsandFeature::gene, andFeatureTrackdraws 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.--isoformsafter 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::transcriptsis 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.assoclays every sequence the--manhattantables 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_associationsreads every row of a table in one pass, andInvocation::genome_widesays when a command line is one. - Several places draw one panel each:
karyon rpoB katG inhA reads.bam genes.gff3draws 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, asno variants here, rather than refusing the figure; a figure of that place alone is still refused.Invocation::moreandPlacecarry the places after the first,stack::build_sheetbuilds 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, whichexamples.zipholds 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::colorsandTraits::colorsgive 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 HEXdraws 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::Heldholds a command line's files in memory by name and reads them asDiskreads them from a path, a BAM through its index and gzipped text out of its wrapper;cli::stack::build_figurebuilds a command line's figure in a given theme and over another window, and says whether it runs along a genome (Built);Invocation::filesnames the files a command line reads, andTrackSpec::sourcesthose of one track. The playground's program reads a page's files as bytes through them, and itscommand,command_regionandcommand_filesdraw a page's command.--with-recombination FILEafter--manhattanlays a genetic map's rate behind the scan's points, read off a scale of its own on the right withcM/Mbafter its highest number, as LocusZoom draws one, and keys it.ManhattanTrack::recombinationdoes it in Rust, andTrack::right_axis_widthlets 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
SNPorIDcolumn 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 FILEafter--squiggleputs 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 bypi:Zandsp:i(read::series::moves,read::bam::named,Files::named_read). The page data gainsmoves.sam.--frequenciestakes--thresholdas the frequency a lineage is flagged at,--growthfor a rise in points of frequency from one time to the next,--min-totalfor the fewest samples a time needs, and--countsto draw counts of samples rather than frequencies.CellScale::Divergingdraws a matrix either side of a centre, each side at full strength at its own furthest value unless a spread is given, and--centersets the centre of a heatmap, as 0 for a log ratio.Legend::divergingkeys 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
--heatmapas 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
Filesit is drawn through. Both were refused. -
A contact map as cooler or Juicer writes it, a
.cool,.mcoolor.hic, is recognised and answered with how to write it as the BEDPE--pairsreads: thecooler dumpcommand for a.cool, and the steps that pick a resolution first for the other two. -
Tree::parsereads Newick or NEXUS, whichever the text is, with its annotations, andTree::parse_allandTree::count_treesread and count a file of several. Tree::support_fromandTreeTrack::support_from, and--support-fromon the command line, read a clade's support from an annotation, as BEAST keeps it inposteriorand MrBayes inprob; the command line refuses a key no clade carries, with the ones they do.read::date::decimal_yearreads a calendar date as a decimal year,Tree::time_valueis the time a node is placed at, andTree::date_from_heightturns 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.
-
NodeRefnames 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::traitsjoins 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, andTreeTrack::joinreturns 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,JoinandSheetare exported from the crate root, andSheet::parsereads one.plot_tree()starts a plot of phylogenies with no made-up locus to hide and no ruler, andPlot::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::parseand 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 FILEdraws 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 holdsgenetic_mapis one when named on its own.read::recombination::ratesreads it, andCoverageTrack::axis_titlesays 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_decimalson the two tracks andread::series::fractional_timesare 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:
--frequenciesdraws counts of each group over time out of a total, as a surveillance programme or an evolution experiment writes them;--phylodynamicsan estimate over time with its interval, EpiEstim's table included;--selectiona test at each site of a gene, HyPhy's FEL and MEME as they are; and--squigglethe current of a nanopore read from SLOW5, which a.slow5named on its own is too, with--readnaming which read. A table's columns are found by their headers, tabs, commas or spaces apart.read::seriesholds the four readers. --heatmap FILEdraws a value per sample per window, asbedtools unionbedgwrites it, and--relativereads each sample against its own median, so a sample sequenced deeper is not a darker row and a deletion is what stands out.MatrixTrack::windowsgives a matrix a column per window, each cell as wide as its window, andread::table::windowsreads the table.--pairs FILEdraws a value between two places, PLINK's.ld, BEDPE or a table headedpos1andpos2, 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.--stylechooses,--thresholdleaves out the weak pairs and--logcolours a contact map.PairTrack,Pair,PairStyle,Plot::add_pairsandread::pairsare the library side, and a.ldor.bedpenamed on its own is one.--ld FILEafter--manhattancolours 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::linkageis the library side.AxisTrack::countingis 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,siteorsample. A ruler of bases wrote a year as2,015, and two thousand samples as2 kb.- A sequential matrix keys its ramp under the figure, from nought to the
value it saturates at, with
MatrixTrack::unitafter 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.pafreads the length the PAF writes forasm1_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 strandandreversed, 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::keygathers them, and the command line draws them. -
--with-tree FILEafter--msa,--snps,--matrixor--domainsorders 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.fastadraws 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::takesasks the grammar whether a track takes an option. -
--rename FROM=TOreads a sequence a file callsFROMas the figure'sTO, so a PLINK table that writes1is drawn beside a FASTA and a BAM that sayNC_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--renamewould draw it, where one plainly would: the one sequence the file names, or the figure's own with achrmore 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 chromosome1. - 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::noteis how the figure tells whoever drew it, andDisk::noteskeeps what it was told. - Two more notes: a BAM named on its own over a thousand bases or fewer says
that
--pileupdraws its reads, since a simulated user drew four hundred bases of depth where the reads were wanted; and bases drawn as blocks say the--widththat would letter them.Figure::px_per_bpis 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.bedthat 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,.tsvor.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-regionor a##contigsays, 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 writes1. Sokaryon rpoB reads.bam genes.gff3 calls.vcf.gzis a whole figure. One position,chr1:18,350, is answered with a span around it. - Every track given no
--labelis called after its file:callsforcalls.vcf.gz,readsfor the reads ofdata/reads.bamandreads depthfor 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,--legendand 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
--traitsstrip is drawn under the figure, each level once however many strips show it.--no-legendleaves 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-sequencereads its reads against the reference the figure's--sequencedraws. 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. -
--manhattanreads 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 asNA. 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.gzor a.bed.gzneeds 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 againstgzip -dcon bgzip,gzip -1and-9, incompressible and concatenated files, at 230 to 500 MB/s. -
A BAM is read as it is by
--coverage,--pileupand--split-reads, through the.baibeside 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::bamcounts depth the waysamtools depth -adoes by default and writes the reads assamtools viewprints 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::Filesis what the builder reads through, any closure from a source to its text being one, andcli::stack::Diskis 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--helpwritten 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 allprints 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 saidstream 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 inkaryon::svgthe pieces the new look is made of:text_width_strongandmono_widthbesidetext_width,fit_text_byfor text measured some other way,TextStyleandSvgWriter::text_styledfor a run of text in another face, weight or spacing, andSvgWriter::fade_down, a fill that fades a colour from the top of a shape to its foot and is written once per colour.CopyNumberTrackand--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.--ploidyis 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::Totalhas no field to put a minor allele in andminor()answers withNone, 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. DynseqTrackand--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, so0.1and0.9both come out at height1.0underProbabilityand both at8.65underLogOdds, andLogoColumn::addclamps 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+2against a-2into 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.
JunctionTrackand--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::dynseqandread::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 readingnMajorwherenMinorwas written is a figure claiming lost heterozygosity in the arms that kept it.SJ.out.tabcounts from one and inclusively on the intron, which is a third convention and neither of its neighbours.- Two examples,
copy_numberandregulation, and the figures they draw. -
A recipe for the cohort copy number landscape, which is a
WindowTrackwith two rows over every span. There is no track type for it because that already draws it:Windowholds 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,DomainTrackandLocusTracktakeTraitsnow, 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., anNAand anything that parses to a number that is not a number are all absent, which is the same three spellingsread::tablealready reads as nothing.--traits <FILE>on the command line, with--columns <A,B,C>to pick the columns and their order.--columnsmay be written before the--traitsit 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
snpsexample 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,--styleand--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
karyondrew 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::buildtakes 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 withwasm-bindgenwould 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::bisulfitereads abismark_methylation_extractorfile, 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::domainreads anInterProScantable, 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 --domtbloutis 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.--bisulfiteand--domains, which is twenty-five of the thirty-three track types the command reaches, and eight left in the library.--contextand--analysis, which join--modificationas 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::methylreads bedMethyl, asmodkit pileupwrites 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 wordNaNinto. 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--modificationsays which, becausemandhat one cytosine stacked on one axis are two marks naming neither.read::structuralreads structural calls out of a VCF.POSis the base before a symbolic event, so it becomes the 0-based start unchanged andENDthe exclusive end unchanged: both conversions are the identity, for two different reasons, and copying the point reader'sPOS - 1moves every call two bases left.SVLENis 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::splitreads molecules that aligned in pieces, from SAM and itsSAtag. 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,--structuraland--split-reads, which is twenty-three of the thirty-three track types the command reaches.--modificationnames the modification a pileup counted.read::cladereads the GFF3 that recombination detection writes,Gubbinsabove 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, becauseGubbinswrites the literalSEQUENCEand 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::locusreads 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.linkstakes BLAST tabular output, which DIAMOND and others write too, or two or three columns of names, and does the join aHomologyneeds: 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.--cladesand--loci, taking their second file by name as--tanglegramdoes:--with-treefor the phylogeny a clade track is painted onto and--linksfor 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.--identitysays 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::buildnow 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::ALLlists the tracks it can draw, for a caller offering a choice of them. - Five more track flags.
--syntenyand--dotplotdraw a PAF fromminimap2as ribbons or as a dot plot;--orfsand--logocompute off the same FASTA and aligned FASTA that--sequenceand--msaalready take; and--tanglegramputs two phylogenies face to face. That is eighteen of the thirty-three track types the command reaches, up from thirteen. read::align_pairsreads 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
TreeTrackbranches can now be orthogonal, diagonal or curved without changing topology;AncestralStateLayer,BranchEventLayerandBranchIntervalLayerkeep state posteriors, direct events and uncertainty on their correct nodes or edges in rectangular, circular and unrooted projections.PhylodynamicTrackadds linear or log trajectories with uncertainty ribbons, whileSurveillanceTrackadds observed lineage counts, denominators, stacked or line views, sampling floors and explicit frequency or growth alerts. Theevolutionary_surveillanceexample 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.
BranchRateMixturedraws weighted ω-class capsules on rectangular, circular and unrooted trees;HomoplasyLayerconnects equal direct branch events as recurrence candidates; andSelectionTrackaligns 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. Theselection_atlasexample combines all three with protein architecture and iTOL-style terminal metadata. Panels::to_svg_with_id_prefix, andPanelsis aDrawing. A sheet numbers its panels' ids fromp0-, 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_svgis what it was.Drawing::region, the region a drawing is laid out along when it has one. AFigureanswers 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,figureandfigures, 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.svgkeeps 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
FeatureRingname 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 ofexample-circular.svg, and left out altogether on the gallery's, which now both name it. --variantsdraws a call whoseAFis., the missing value bcftools writes for a fraction it could not work out, full height, as it draws a call with noAF:AF=.refused the whole track withAF is not a number: ".". On a row of several alternatesAF=0.5,.gives the first its fraction and the second none.--structuralreads anENDor anSVLENof.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, whichread::bam::Bgzfnames, 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--coveragetrack 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##gwas 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
--widthfor 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##fof its first line and was refused for having no#CHROMline, a SAM the@HDof 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
POSa file can write panicked--variantsand--genotypeswith an overflow, adding to it the length of whatREFspells. The end saturates, and the row is left out of any window that ends before it. -
--variantsdrew a gVCF's placeholder allele as a call: a GATK row writtenT,<NON_REF>was a substitution and a second lollipop callednon_refat 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. --matrixgiven 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--genotypesdraws its samples.Traits::colorsreaches every column of its key, the ones made after the call as well as before it: given beforeTraits::stripsit 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 depthcommand 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
--shadeor 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 isforkaryon 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.nwkit 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::squiggleandread::series::movesfor 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_tipsis the cut, as ape'skeep.tipmakes 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::warningshands 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,
circularundid a fan, a scale bar's length or unit brought back a bar hidden withshow_scale_bar(false),dndsandcolor_byeach cleared the other so the last one won, and a tanglegram'sshow_tipsput 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::legendkeys the values and the disagreement forFigure::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_categoricalboth started at the palette's first colour, so each lineage was also a country. A column that chose its start withfirst_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::spreadchose 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
presentandabsentwith 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::symbolwrapped with the colour every twelve levels of the six-colour palette, so the thirteenth level was drawn exactly as the first; withTheme::colorof the same index it now tells four times the palette's length apart, from whichever colour a column starts at. Figure::keynames a phylogeny's colours, in the figure's own theme.TreeTrackdid not implementTrack::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.
--featuresdraws 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, namedANONYMOUS, the gene, its transcript, two exons and the CDS. A GFF3 row whoseParent=orDerives_from=names a row in the file is now left out, so is aregion,chromosome,scaffoldor 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_nameandgene_id, where every GTF gene was drawn nameless beside its own CDS.--manhattanreads a column of p-values as one. A table headedP,pvalue,p.value,p_wald,P_BOLT_LMMor another name every association tool writes, or a q-value orFDRcolumn, is drawn as-log10of 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.--thresholdis then a p-value as well, since it is given in the file's units, so5e-8draws the linegenome-widedoes and7.3is 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-wideis refused on a phylogeny, where it was read as a support value of 7.3.read::point::association_tablereturns 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,--dynseqand 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
--labelmeant for the next track and written before its flag renamed this one, and a second--heightundid the first.--highlight, which adds clades to a list, and the flags that only switch something on still take any number. -orefuses a name that promises a format karyon does not write, such asfig.png,fig.pdforfig.svgz, and says to write SVG and convert it. The figure was written as SVG under the name, and the command exited nought.--heighttakes a number of pixels above nought, as--row-heightalready did.NaNparsed 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,--tanglegramand--snpstracks 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.svgandexample-tanglegram.svglosesites:1-34andtaxa:1-8from their<title>, and nothing drawn changes. An ideogram marks the window on its chromosome and keeps its locus;Track::shows_regionsays 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
Traitsmakes now carries the order its levels are coloured in,Traits::from_sheettakes 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;--traitsdoes all of this.Sheetgainsorderandlevels, andTraitColumngainslevelsandlevel_order. TreeTrack::legendkeys 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.svgdrew the same sample in two shades in its open and its folded panel, and draws it in one.Traits::legenddraws 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 withfill="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 atvisual_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. --methylationdraws 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-readsthat hides every call already drew the band with its count, and a window with nothing in it at all is still refused.HomoplasyLayerreads a list annotation one event at a time, asBranchEventLayeralready 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,dndsandbranch_labelsstarted their layer afresh, so a unit, a direction or a hidden axis written beforetime, adnds_setting written beforedndsand a label size written beforebranch_labelswere dropped without a word. A reroot turned the root marker back on aftershow_root(false). Andmax_rowsfolded 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, acollapseafter it folded one clade more on a tree already fitted to the cap, andmax_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.PhyloMapdropped a direction or a unit written beforetimethe same way, and no longer does. Every committed figure is byte for byte what it was. ManhattanTrackkeeps 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.svgand 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::associationssaid 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--thresholdalready said, so a table is written as-log10(p), andAssociation::from_p_valueconverts 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
--heightcontrol 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'scli::stack::open_from_diskreads paths and standard input; it now says drawing does no I/O and names that one function. Its list of whatreadtakes had stopped at nine formats and now names the rest, from PAF and bedMethyl to InterProScan's table. The formats page said--coverageand--windowsrefuse a value that is not a number, andnanandinfare read as missing and leave a gap; it says so.add_windowsoffered bars, whichWindowStyledoes not have. - A line stops at a missing value and starts again after it, rather than running
straight across. A coverage track in the
AreaandLinestyles joined the last column before a gap to the first one after, so a hundred bases ofNaNin a three hundred base profile, or a bedGraph row readingnan, came out as a 282 pixel segment at a depth nothing measured, filled underneath in the default style, whileBarsand the track's own documentation left the stretch empty. The same bridge crossed a window with no value or a stretch nobody windowed in aWindowTrackdrawn as a line, samples with no current in a zoomedSquiggleTracktrace, an estimate aPhylodynamicTrackcould not place on its axis, and, in aSurveillanceTrack, 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::Maxno 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::Minshows 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
widthis called, so a margin or a label gutter counts the same written beforewidthas after it. The floor is the smallest width that leaves a plotting area, and it was worked out from the margins andlabel_widthas 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, throughplot()as well, and aPanelssheet 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 unlesswidthwas called again afterwards. Alabel_widththat 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 aswidth="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
snpsexample labelled 1472251 a site a VCF writes at 1,472,252.SnpSite::positionis 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 answersfalsetoTrack::on_coordinates, and so doIdeogramTrackandLegendTrack, 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-axisis 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 inexample-snps.svg, and in the variable-site panel ofgallery.svg, is one higher. - Six command line flags that were accepted and did nothing now do what they say
or are refused by name.
--carryingwithout--mutationsput 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--mutationsnames the key the changes are kept under.--mutationswithout--carryingread the changes and drew nothing with them; it is refused the other way round, since only--carryingasks anything of them.--color-bywith a key no node carries drew every branch in one colour; it is refused with the keys the tree does carry.--heightafter--copy-numberwas 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.--formatafter any track but--coverage,--featuresand--loci, or with a word the track before it does not read, such asdepthafter--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 newMethylationTrack::no_coverage. --sequenceand--orfstake a FASTA's record by the sequence the region names, as--with-sequencealready 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-sequencerefuses 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::mutedclaimed the track labels, which are set in the foreground ink, and now claims the tick labels it does colour.BaseColors::colorblind_safesaid 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 ofread, and it isTree::parse_newickthat reads it. The structural VCF reader said taking one offPOSwould move every call two bases, where its own audit test shows one.CodonTrack::show_startsaid 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
mainafter a merge.BuildError::Unnamedsaid 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-tonames. - A list of changes written in braces is read whole.
Mutations::readtook[&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.--carryingthen 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 byMutations::unreadrather than dropped without a word.Mutation::parse_listtakes 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
clamppanics 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::legendtakes 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
NAfor 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. --sampleon 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
--dynseqreference 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
--dynseqtrack 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_posandend_posis read as 1-based like thestartposit 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.
--heightis accepted on--copy-numberand--dynseq, both of which size themselves by a field and were refusing a flag that means something to them.--ploidy 0is 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,TraitScaleandTraitStylemoved fromtrack::treetotrack::traits, unchanged, and are still exported fromkaryon,karyon::trackandkaryon::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
--helpprints 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:
Genomeadded 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
f64into text; there are now three, and the two that remain outsidesvgare four-line wrappers over them. Two of the copies were literally identical and a third was the same plus a check forNaNthat 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_roundedandsvg::text_exactare the two ways a number reaches a reader, and both carry the guards;svg::numis unchanged and remains the way a number reaches an attribute.svg::text_number, added earlier in this same unreleased version, is gone in favour oftext_rounded. svg::finite_withinreplaces 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 forNaN, so there is now one answer to what happens then. Four pixel thresholds that had no answer at all, inLogoTrack,PileupTrackandSequenceTrack, use it.- The three largest files became directory modules, split along what they
actually do rather than at a line count.
src/track/tree.rswas 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 inmod.rs.src/map.rsbecame 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.rsgave up its Newick, annotated Newick and NEXUS parsing totree::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.Formatmoved 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:
--aggregateset 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.bgleftgenes.gff3holding 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-draggingwith it. - A region the pan and zoom arithmetic was happy to reach stopped the program
dead, and the page said nothing.
MAX_SPANwas1 << 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, aVeccannot exceedisize::MAXbytes, so a per-base track of1 << 28bases is a capacity overflow, which is a panic, which in a build that aborts on one is a trap with noResultto 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,503over 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,000five 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
--labelthat 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-modaland did not mean it: with it open the page behind was still reachable by Tab. It is a realdialogopened withshowModal, 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
closeevent, which a listener added directly in front of aclose()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 screenwas 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
bodyand 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 100was 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
u64in 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.
mixblends two colours by an amount,clamppropagates a NaN, andNaN as u8saturates 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::identityisOption<f64>rather thanf64, matchingAlignmentBlock. Two genes matching with nobody stating how closely is an ordinary case, andHomology::newnow records a NaN as the absence it is instead of clamping it into the range.Homology::unstatedsays 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::identityhad the same hole and is closed the same way.- A tree annotation whose number is not a number printed as
NaNin 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 anf64, it parses to one, and reporting it verbatim says what the file said. tests/properties.rschecks tooltips as well as attributes. The writer's guard covers numbers written into attributes, and a tooltip is a caller's number put throughformat!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
--syntenywith nothing after it reported that--axisneeded 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. SquiggleTracknever 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.Genomeadded lengths and gaps without saturating, in all seven places it accumulates. A debug build panicked; a release build wrapped, which is worse, becauseboundariesthen 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"whileFigure::dimensionssaid 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_numberis the sibling of it for numbers a person reads, agreeing withnumto the byte below two to the fifty-third and switching to an exponent above it. This was the third place the same mistake appeared, afterVariantTrackandMap, which is why it now has a function of its own rather than a third patch. - A map tooltip printed
f64::MAXas three hundred and nine digits. The same bug as the one fixed inVariantTrack, in the other writer, found because the properties had never been pointed at a map:data_numberhanded 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, becausegit diffcompares 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.dndsuses 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_significancecan 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
Panelsgrids 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
DomainTrackfor 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"andaria-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::descriptionsets 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_titledandSvgWriter::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_inertfor 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.plotandPlot, a short way to write a figure down.Figuretakes 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. ARegionto unwrap, auseline naming every track type, apusharound each one, anAxisTrackto 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 oneadd_call per track fills it, in the order they stack.Plotholds the track added last in its type parameter, soPlot::labelnames it andPlot::adjusthands 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
AxisTrackgoes on the bottom, since a figure without coordinates along it is rarely what anyone meant;Plot::add_axisputs it elsewhere andPlot::remove_axisleaves it out. Tracks carrying an array rather than coordinates start at the left edge of the region, with an_atform 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 makesadjustwork is what takes that away.Plot::add_trackandPlot::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 andadjustwould 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 insrc/bin/karyon/. The grammar is the facade with spaces instead of dots, becauseargvis 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 viewandbcftools viewalready 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 depthare 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,CladeandPlacementat the crate root.TreeTrack,CladeTrackandTanglegramTrackwere re-exported there and all three take aTree, which was not.- Typed phylogenetic annotations through
parse_annotated_newickandparse_nexus, including BEAST, NHX, rootedness markers and Nexus translation tables.Treenow exposes MRCA, ancestor and descendant queries, rotation, ladderising, internal-node rerooting, subtree extraction and collapse. - Dated
TreeTracklayouts, 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
TreeTrackprojections 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,GeoFlowand 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.PhyloMapandPhyloConnector, 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 returnio::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 forunwrap, and it was the second line of all fourteen chains in the examples.Empty,SlotandNamed, the plumbing that givesPlotits type parameter, underkaryon::plotrather than at the crate root.Emptyis deliberately not aTrack, solabelandadjustbefore 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.SlotandNamedare 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,SplitReadandSplitSegment, reads that align in pieces. APileupTrackread is one start, one CIGAR and one strand, so a molecule that visits three places cannot be written down in it, and aStructuralTrackarc 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.CladeTrackandCladeBlock, genomic intervals painted onto a phylogeny. AMatrixTrackcell 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,TranscriptionUnitandTerminator. 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.rsand 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::MAXas 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: anf64holds 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 whatu64::MAXneeds 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::endand the CIGAR walk, the codon walk inOrfTrack, 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 throughf64, sinceu64::MAX - 10is not representable, rounds to 2^64 and comes back asu64::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
OrfTrackreported 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. MatrixTrackput 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_inkchose the less legible ink for adenine and guanine. The contrast ratios say so, and now the ink follows them.AxisRingplaced 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.
Panelssized the sheet from the panel widths alone, so a caption could be painted over the next column.Panels::share_outdid not return the most level partition among those tying on the tallest column.FeatureTrackpacked 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::atdid 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 positionsGenome::locateinverts, which is a narrowing: the end of a half-open interval isGenome::offset, notat.
Data dropped without a word.
CodonTrackdrew 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_newicksilently 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::defaultwhile 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::linkcollapsed the origin-spanning end of a chord to a hairline.--matrixwhose sites all fall outside the region was a silently empty lane and exit 0.
Output that was not valid SVG.
--colorwas 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::numwrote the literalinffor finite inputs abovef64::MAX / 1000, which was the one guard the writer had against a non-finite number reaching the page.escapelet 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.
u64coordinate arithmetic overflowed across eight constructors.- A large finite
Figure::widthpanicked with a capacity overflow. - A named origin-spanning feature panicked
FeatureRingwith 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 onePanelsputs 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 anotherrole="img"also hid its contents from a screen reader rather than describing them. SnpTrackhung its tooltip on the rotated column label rather than on the data, so the panel itself answered nothing, andshow_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.MatrixTrackcounted 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 read101 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 themfigure.rsat 462 lines of code,theme.rs,codon.rsand all four command line readers, whilebisulfite.rscarried 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
## Nextblock 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.
rpoBwas 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 frameCodonTrackpins in its own doctest;RD1was drawn at 8 kb, on top of gyrA and gyrB, rather than at 4.35 Mb;eccCa1was drawn on the reverse strand when its locus tag has nocsuffix. 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,FrequencyTrackandFrequency, with their examples, assets and gallery panels. All three tracks failed the only entry test this crate has: none of theirdrawmethods ever readsctx.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, andsrc/track/distance.rshad 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
Trackto compute it.
Fixed¶
MethylationTrack::hemimethylatedonly paired the two strands when both calls sat on the same coordinate, which is never true of a palindrome: the two 6mA of aGATCare one base apart, and so are the two 5mC of aCpG. 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 withinMethylationTrack::pair_within, one base by default.Panels::columnsnow 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.svgandexamples/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 orderingSnpTrackalready 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::consensusbroke ties by the last residue seen rather than the first, contradicting its own documentation. It usedmax_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_dispatchonly. Pushing no longer spends Actions minutes; run it from the Actions tab or withgh 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, withTree::parse_newickreading 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, withTreeShapechoosing 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¶
SnpTrackandSnpSite, 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_alignmentfinds 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
SnpTrackis 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 anAxisTrackdoes 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¶
MsaTrackandMsaSequence, 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 toDifferences. 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_tonames 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.MsaColoringandResidueClass, 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_lengthor a window of it withtarget_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¶
ManhattanTrackandAssociation, association statistics along the sequence with a threshold line and hits coloured and ringed above it.Association::from_p_valueconverts a p-value to-log10(p)and floors a zero at the smallest positive double rather than letting it become infinity.genome_wide_thresholdis the conventional-log10(5e-8), documented as a Bonferroni correction for a million independent tests and therefore the wrong number for most organisms.MatrixTrack,MatrixRowandCellScale, 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::Sequentialfor quantities and presence,CellScale::Categoricalfor 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.
MatrixTrackgives 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_scaleturns 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, andDrawContext::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 sharedScale, because a track that only showed the region on display could not say where that region is.BandandStain, withStain::from_namereading thegieStaincolumn of the UCSCcytoBandtable 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::barefor a chromosome with no banding, which is what a bacterial genome wants, andIdeogramTrack::highlightfor 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_strokedandSvgWriter::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 namedBaseColors::conventional, with their measured weakness stated: adenine and guanine are 1.7 apart under protanopia, which is the transition pair.Theme::corner_radiusandTheme::insertion, the latter so a pileup's insertion marks stop being a hardcoded hex.SvgWriter::rect_roundedandSvgWriter::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.CigarOpandRead, 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,=andXall becomeMatch: the track compares the sequences itself rather than trusting the operation.Read::segments, which walks the CIGAR and places every piece on the reference, andRead::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::layoutreturns that count.ReadColoring::Strandfor strand bias at a glance, andPileupTrack::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::stabilizeandLogoTrack::stabilize_withto apply it, andLogoTrack::sample_sizefor 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, throughDashFit::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::smoothingis 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) andLogoScore::OddsRatio. Together withProbability,InformationContentandLogOddsthese cover theLogolasset. 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 thequantargument ofLogolas;Centering::Noneleaves 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_backgroundandLogoScore::unit.- A third example figure,
assets/example-logo-scores.svg, showing the same four columns under all five background-relative scores.
Changed¶
LogoScalingis nowLogoScore, andLogoTrack::scalingisLogoTrack::score. The oldEnrichmentDepletionvariant was the pairing of one score with one centring, which is now spelledLogoTrack::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 bitswhile 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 tolog2(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 classicseqLogoplot in bits) andEnrichmentDepletion, which scores each symbol aslog2(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 plotLogolascalls 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_sequencesto count an alignment, andLogoTrack::from_matrixto take a position weight matrix.LogoTrack::backgroundfor a non-uniform background,alphabet_sizefor counting symbols that never appear,smoothingfor how loudly absence speaks,symbol_colorfor per-symbol colours, andorderfor which end of the stack the tallest symbol goes.SvgWriter::glyph, which stretches one glyph to an exact box usingtextLength, 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¶
Themegained a field, so any code building one with a struct literal rather than fromTheme::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, withRegion::parsefor the 1-based inclusive locus strings used by samtools and IGV, and aDisplaythat round-trips through it.Scale, the shared mapping between base positions and pixels, withx,x_center,pos_at_xand the two resolution helpers.CoverageTrackfor 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.SequenceTrackfor the reference bases, drawn as letters, as coloured blocks or as a zoom hint depending on how many pixels a base gets.FeatureTrackfor 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.VariantTrackfor 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.