# 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](https://github.com/TomFrost/Jexl) for defining configuration
callbacks, which look like this:

```json
"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:

```js
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()` {#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`](https://jbrowse.org/jb2-staging/docs/config_guides/customizing_feature_details)       | 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:

```json
"color": "jexl:{CDS:'red',exon:'green',gene:'blue'}[feature.type] || 'gray'"
```

Color by a threshold, a ternary on a numeric attribute:

```json
"color": "jexl:feature.score > 7.3 ? 'red' : '#0068d1'"
```

Label with a fallback. The first non-empty attribute wins:

```json
"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:

```json
"formatDetails": {
  "feature": "jexl:{UniProt:'https://www.uniprot.org/uniprotkb/'+feature.uniprot_id, phase:undefined}"
}
```

See
[customizing feature details](https://jbrowse.org/jb2-staging/docs/config_guides/customizing_feature_details).

The
["Jexl callback examples" track](https://jbrowse.org/code/jb2/main/?config=test_data/config_demo.json&assembly=hg19&tracks=jexl_callbacks_demo_hg19)
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.

<!-- JEXL_CATALOG START -->

**Math functions**

```js
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**

```js
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**

```js
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**

```js
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**

```js
jexl: log(feature) // console.logs output and returns value
```

**Binary operators**

```js
jexl: feature.flags & 2 // bitwise and to check if BAM or CRAM feature flags has 2 set
```

**Slot defaults from plugins**

```js
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**

```js
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"))
```

<!-- JEXL_CATALOG END -->

The catalog above is generated from the registrations themselves — core's in
[`packages/core/src/util/jexl.ts`](https://github.com/GMOD/jbrowse-components/blob/main/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 [](https://jbrowse.org/jb2-staging/docs/config_guides/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:

```json
"color": "jexl:`hsl(${feature.start/100000},50%,50%)`"
```

The equivalent with concatenation:

```json
"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:

```json
"color": "jexl:colorFeature(feature)"
```

The plugin is a single file with no build step. See
[customizing feature colors](https://jbrowse.org/jb2-staging/docs/config_guides/customizing_feature_colors) for
the color version and
[customizing feature details](https://jbrowse.org/jb2-staging/docs/config_guides/customizing_feature_details)
for reshaping detail panels the same way.

## See also

- [](https://jbrowse.org/jb2-staging/docs/config_guides/customizing_feature_colors)
- [](https://jbrowse.org/jb2-staging/docs/config_guides/customizing_feature_details)
- [](https://jbrowse.org/jb2-staging/docs/user_guides/variant_track)
- [](https://jbrowse.org/jb2-staging/docs/user_guides/alignments_track)

