Skip to content

RefSeq Gene Annotations with Scored Labels

This example shows a RefSeq gene annotation track for hg38. Transcript bodies and exons are packed into lanes to reduce overlap, and the gene symbols use measureText and filterScoredLabels to keep the most useful labels visible as the view changes. The prioritized gene symbols act as landmarks for navigating the genome, and the small arrows visible at high zoom levels indicate transcript direction.

{
  "description": [
    "RefSeq Gene Annotations with Scored Labels",
    "Shows hg38 RefSeq gene annotations with transcript bodies, exon rectangles, scored gene labels, and strand arrows that appear at detailed zoom levels.",
    "Data source: RefSeq gene annotations from NCBI, mirrored as a preprocessed GenomeSpy TSV with citation-count scores and compressed exon intervals."
  ],

  "assembly": "hg38",

  "name": "refseq-track",

  "title": {
    "text": "RefSeq Gene annotation",
    "orient": "none"
  },

  "height": { "step": 23 },

  "data": {
    "url": "https://data.genomespy.app/genomes/hg38/refSeqGenes-hg38-release232.tsv.gz",
    "format": {
      "parse": {
        "symbol": "string",
        "chrom": "string",
        "start": "integer",
        "length": "integer",
        "strand": "string",
        "score": "integer",
        "exons": "string"
      }
    }
  },

  "transform": [
    {
      "type": "linearizeGenomicCoordinate",
      "chrom": "chrom",
      "pos": "start",
      "as": "_start"
    },
    {
      "type": "formula",
      "expr": "datum._start + datum.length",
      "as": "_end"
    },
    {
      "type": "formula",
      "expr": "datum._start + datum.length / 2",
      "as": "_centroid"
    },
    {
      "type": "collect",
      "sort": { "field": ["_start"] }
    },
    {
      "type": "pileup",
      "start": "_start",
      "end": "_end",
      "as": "_lane",
      "preference": "strand",
      "preferredOrder": ["-", "+"]
    },
    {
      "type": "filter",
      "expr": "datum._lane < 3"
    }
  ],

  "encoding": {
    "y": {
      "field": "_lane",
      "type": "ordinal",
      "scale": {
        "type": "index",
        "align": 0,
        "paddingInner": 0.4,
        "paddingOuter": 0.2,
        "domain": [0, 3],
        "reverse": true,
        "zoom": false
      },
      "axis": null
    }
  },

  "layer": [
    {
      "name": "transcripts",

      "opacity": {
        "unitsPerPixel": [100000, 40000],
        "values": [0, 1]
      },

      "encoding": {
        "color": { "value": "#909090" }
      },

      "layer": [
        {
          "name": "exons",

          "transform": [
            { "type": "project", "fields": ["_lane", "_start", "exons"] },
            { "type": "flattenCompressedExons", "start": "_start" }
          ],

          "mark": {
            "type": "rect",
            "minOpacity": 0.2,
            "minWidth": 0.5,
            "tooltip": null
          },

          "encoding": {
            "x": { "field": "exonStart", "type": "locus" },
            "x2": { "field": "exonEnd" }
          }
        },
        {
          "name": "bodies",

          "title": "Gene annotations",

          "mark": {
            "type": "rule",
            "minLength": 0.5,
            "size": 1,
            "tooltip": null
          },
          "encoding": {
            "x": {
              "field": "_start",
              "type": "locus",
              "axis": { "title": null }
            },
            "x2": { "field": "_end" },
            "search": { "field": "symbol" }
          }
        }
      ]
    },
    {
      "name": "symbols",

      "transform": [
        {
          "type": "measureText",
          "fontSize": 11,
          "field": "symbol",
          "as": "_textWidth"
        },
        {
          "type": "filterScoredLabels",
          "lane": "_lane",
          "score": "score",
          "width": "_textWidth",
          "pos": "_centroid",
          "padding": 5
        }
      ],

      "layer": [
        {
          "name": "labels",
          "mark": {
            "type": "text",
            "size": 11,
            "yOffset": 7,
            "tooltip": {
              "handler": "refseqgene"
            }
          },
          "encoding": {
            "x": {
              "field": "_centroid",
              "type": "locus"
            },
            "text": { "field": "symbol" }
          }
        },
        {
          "name": "arrows",
          "opacity": {
            "unitsPerPixel": [100000, 40000],
            "values": [0, 1]
          },
          "mark": {
            "type": "point",
            "yOffset": 7,
            "size": 50,
            "tooltip": null
          },
          "encoding": {
            "x": {
              "field": "_centroid",
              "type": "locus"
            },
            "dx": {
              "expr": "(datum._textWidth / 2 + 5) * (datum.strand == '-' ? -1 : 1)",
              "type": "quantitative",
              "scale": null
            },
            "color": { "value": "black" },
            "shape": {
              "field": "strand",
              "type": "nominal",
              "scale": {
                "domain": ["-", "+"],
                "range": ["triangle-left", "triangle-right"]
              },
              "legend": null
            }
          }
        }
      ]
    }
  ]
}

The gene annotation track was inspired by HiGlass. The genes are scored by their citation counts, overlapping isoforms are merged into a single virtual isoform that includes all exons, and the annotations were preprocessed with compressGeneAnnotations.py.

What to notice

GenomeSpy's hierarchical dataflow lets the symbol labels update dynamically as the user pans and zooms. The view has two main layer groups:

  • Transcripts
    • Exon rectangles
    • Body rules
  • Symbols
    • Labels
    • Strand arrows

Of the roughly 30,000 gene symbols, only the highest-scoring symbols in the visible genomic region are shown when there is room.

The transcript layer fades in only after the view is zoomed close enough. This keeps the overview uncluttered while still showing exon structure and transcript direction at detailed scales. The custom tooltip handler fetches RefSeq gene summaries for visible labels.

Data and Label Selection

The source file contains one compressed row per gene symbol. Overlapping isoforms have already been merged into a single interval with a compressed exon list, which keeps the data compact enough for an annotation track that spans the whole genome. The spec linearizes genomic coordinates before sorting and lane assignment so the transforms can compare intervals across chromosomes.

Label selection is separate from transcript rendering. First, the symbol text is measured in pixels. Then filterScoredLabels watches the visible genomic domain, tries higher-scoring genes first, and keeps labels that fit without overlap in their lanes. This lets familiar genes remain visible as landmarks without filling the track with every annotation in the current region.

GenomeSpy Features

This example combines several GenomeSpy capabilities in one spec:

  • linearizeGenomicCoordinate converts chromosome and base-pair positions into sortable coordinates used by later transforms.
  • collect sorts genes before lane assignment.
  • pileup packs transcripts into lanes and prefers strand-specific ordering.
  • flattenCompressedExons expands compressed exon intervals into individual exon items.
  • Semantic zooming fades transcript bodies, exons, and strand arrows in as the horizontal scale reaches detailed genomic ranges.
  • measureText measures label widths for fitting.
  • filterScoredLabels chooses high-scoring labels that fit in the visible region.
  • The search channel exposes gene symbols to GenomeSpy's search behavior.