Using jexl callbacks
TL;DR: a config callback is a string prefixed with jexl:. Read feature
attributes as plain properties (feature.strand). When an expression outgrows
one line, register your own function from a small plugin and call it like a
built-in.
We use Jexl for defining configuration callbacks, which look like this:
"color": "jexl:feature.strand==-1?'red':'blue'"
Feature operations
Read any feature attribute as a plain property, e.g. feature.strand. Nested
attributes work too (feature.INFO.SVTYPE), and feature.parent gives the
parent feature:
jexl: feature.start // start coordinate, 0-based half open
jexl: feature.end // end coordinate, 0-based half open
jexl: feature.refName // chromosome or reference sequence name
jexl: feature.CIGAR // BAM or CRAM feature CIGAR string
jexl: feature.seq // BAM or CRAM feature sequence
jexl: feature.type // feature type e.g. mRNA or gene
jexl: feature.id // the feature's id attribute, e.g. a GFF3 ID=
jexl: feature.parent // parent feature, e.g. the gene of an mRNA (undefined if none)
Property access vs get()
feature.start (property access) and get(feature,'start') (function form) are
equivalent. The get() form works on every JBrowse release, while property
access was added more recently, so prefer get() if your config must run on
older versions. Otherwise use whichever reads more clearly. The examples in this
guide use property access.
What feature actually is depends on which callback you are in:
| Callback | feature is | Property form | get() form |
|---|---|---|---|
Color, label, tooltip, filter (color, name, mouseover, filterBy) | a SimpleFeature | yes | yes |
formatDetails | a plain object from the session | yes | no |
formatDetails runs against the serialized feature the detail panel holds, not
a SimpleFeature, so feature.get('start') fails there. Property form works
everywhere.
In JavaScript plugin code the rule is different again: a SimpleFeature handed
to your own function is the real object, so use feature.get('start').
Common patterns
A few callbacks cover most real configs:
Color by feature type. Index a lookup table by an attribute, with a default for types not in the map:
"color": "jexl:{CDS:'red',exon:'green',gene:'blue'}[feature.type] || 'gray'"
Color by a threshold, a ternary on a numeric attribute:
"color": "jexl:feature.score > 7.3 ? 'red' : '#0068d1'"
Label with a fallback. The first non-empty attribute wins:
"name": "jexl:feature.name || feature.id"
Add a row to the click-details panel. formatDetails is the one slot family
whose callback returns an object rather than a single value: each key
becomes a field, and a key set to undefined hides one. Returning a bare value
here produces no rows:
"formatDetails": {
"feature": "jexl:{UniProt:'https://www.uniprot.org/uniprotkb/'+feature.uniprot_id, phase:undefined}"
}
See customizing feature details.
The "Jexl callback examples" track on the hosted demo config combines a lookup-table color with a template-string mouseover.
Other functions available in jexl include the categories below. The getTag
function smooths over slight differences in BAM and CRAM features to access
their tags.
Math functions
jexl: max(0, 2)
jexl: min(0, 2)
jexl: sqrt(4)
jexl: ceil(0.5)
jexl: floor(0.5)
jexl: round(0.5)
jexl: abs(-0.5)
jexl: log10(50000)
jexl: parseInt('2')
jexl: parseFloat('2.054')
String functions
jexl: charAt('abc', 2) // c
jexl: charCodeAt(' ', 0) // 32
jexl: codePointAt(' ', 0) // 32
jexl: startsWith('kittycat', 'kit') // true
jexl: endsWith('kittycat', 'cat') // true
jexl: padStart('cat', 8, 'kitty') // kittycat
jexl: padEnd('kitty', 8, 'cat') // kittycat
jexl: replace('kittycat', 'cat', '') // kitty
jexl: replaceAll('kittycatcat', 'cat', '') // kitty
jexl: slice('kittycat', 5) // cat
jexl: substring('kittycat', 0, 5) // kitty
jexl: trim(' kitty ') // kitty, whitespace trimmed
jexl: trimStart(' kitty ') // kitty, starting whitespace trimmed
jexl: trimEnd(' kitty ') // kitty, ending whitespace trimmed
jexl: toUpperCase('kitty') // KITTY
jexl: toLowerCase('KITTY') // kitty
jexl: split('KITTY KITTY', ' ') // ['KITTY', 'KITTY']
jexl: split(feature.notThere, ' ') // [''], an absent value is read as the empty string rather than throwing
jexl: join('-', 'a', 'b', '', 'c') // a-b-c, joins truthy args with the separator
jexl: includes('kittycat', 'cat') // true
jexl: repeat('ab', 3) // ababab
jexl: jsonParse('{"a":1}') // parses a JSON string
Feature operations - getTag
jexl: getTag(feature, 'MD') // fetches MD string from BAM or CRAM feature
jexl: getTag(feature, 'HP') // fetches haplotype tag from BAM or CRAM feature
Color functions
jexl: randomColor(feature.type) // deterministic color from a string (e.g. a feature type)
jexl: alpha('green', 0.5) // a color at 50% opacity
jexl: hsl('#ff0000') // converts a color to its HSL form
jexl: colorString('green') // normalizes a color name or value to a hex string
Console logging
jexl: log(feature) // console.logs output and returns value
Binary operators
jexl: feature.flags & 2 // bitwise and to check if BAM or CRAM feature flags has 2 set
Slot defaults from plugins
jexl: logThickness(feature, 'score') // log(attribute + 1), the arc display's default thickness
jexl: defaultPairedArcColor(feature, alt) // a color per SV type read off the ALT (DEL, DUP, INV, TRA, CNV)
jexl: lgvSyntenyTooltip(feature) // both sides of a synteny feature, the LGVSyntenyDisplay's default mouseover
jexl: defaultOnChordClick(feature, track, pluginManager) // opens a breakpoint split view on the clicked chord
jexl: svChordColor(feature) // the SV-type color the inspector's chords are drawn in
Variant functions
jexl: maf(feature) // minor allele frequency over the called alleles
jexl: missingness(feature) // fraction of samples with no call
jexl: impact(feature) // HIGH, MODERATE, LOW or MODIFIER, from SnpEff ANN / VEP CSQ
jexl: consequence(feature) // e.g. missense_variant, from the same annotation — the MOST SEVERE one alone
jexl: 'missense_variant' in consequences(feature) // every consequence term on the record, across all transcripts (bcftools INFO/CSQ ~ "missense_variant")
jexl: impactColor(feature) // the color the "Color by consequence impact" menu item uses
jexl: svTypeColor(feature) // the color "Color by SV type" uses
jexl: alleleLength(feature) >= 50 // longest allele in bp, so an insertion is not measured by its reference span
jexl: svType(feature) == 'DEL' // SV class, read off a symbolic ALT before falling back to INFO/SVTYPE (bcftools INFO/SVTYPE)
jexl: nAlt(feature) == 1 // ALT alleles the record declares, i.e. biallelic-only (bcftools N_ALT)
jexl: genotypeCount(feature, 'het') > 0 // samples in a genotype class — ref, alt, hom, het or mis (bcftools N_PASS(GT="het"))
The catalog above is generated from the registrations themselves — core's in
packages/core/src/util/jexl.ts,
and each plugin's alongside the display it serves. A plugin you install can add
more; those are documented by the plugin.
The last two groups come from plugins that ship with JBrowse:
- the variant functions are the same ones the variant track's filter and color menus write for you, so a menu choice can be copied into a config and then edited (see Variant track)
- the slot defaults are what those slots already evaluate to unconfigured, listed so you can compose with one
Template strings
Our jexl fork supports JavaScript-style template literals with backticks and
${...} interpolation, handy for building colors, for example an HSL color
derived from a feature value:
"color": "jexl:`hsl(${feature.start/100000},50%,50%)`"
The equivalent with concatenation:
"color": "jexl:'hsl('+feature.start/100000+',50%,50%)'"
Adding your own jexl function
Jexl has no way to define a variable or a branchy helper, so past a certain point an expression stops being readable. The escape hatch is to add your own function to the jexl language from a small plugin and call it like any built-in:
"color": "jexl:colorFeature(feature)"
The plugin is a single file with no build step. See customizing feature colors for the color version and customizing feature details for reshaping detail panels the same way.