Skip to content

Values

The "values" transform projects a field into an array parameter while passing all input rows onward. For example, it can load precomputed cluster boundaries from a file or publish boundaries calculated from metadata for an index scale's gapPositions.

Parameters

description

Type: string

A description of the transform step. Can be used for documentation and agent context.

field Required

Type: string (field name)

Field whose scalar values form the output array, preserving order and duplicates.

param Required

Type: string | object

Publishes the array as a read-only parameter.

A string such as "categories" creates the output in this view. Use { "name": "categories", "push": "outer" } to publish into the nearest ancestor that declares { "name": "categories" } in its params. The ancestor declaration accepts only name and an optional description.

Claimed outputs are read-only and start with an empty array when the producer view is constructed. Each output has exactly one producer.

Output parameters

A string param declares a read-only parameter in the owning view's scope. Do not also declare that local output in params. Descendant views can read the output in expressions.

To share a child transform's output with its ancestors and siblings, declare { "name": "categories" } in the common ancestor's params and use "param": { "name": "categories", "push": "outer" } on the transform. This claims the nearest ancestor declaration with that name. The declaration accepts only name and an optional description, and cannot also receive updates from interactions or other pushed parameters. The transform makes it read-only, with exactly one producer. Its producer remains active when the child's presentation is hidden.

Local outputs start as an immutable []. A bare ancestor parameter starts as null and becomes [] when its child producer is constructed. Declare it before dependent parent expression parameters, and make those expressions tolerate null, for example categories ? length(categories) : 0. Existing expressions continue observing the same parameter. A bare parameter without a producer remains an ordinary writable parameter.

Construct the owner and its pushed producer together. A dynamically inserted subtree can contain both, but cannot claim a parameter on an already configured ancestor. Replace the producer's data to update the output; removing the producer requires disposing its owning composition as well.

Each successfully completed input publishes an immutable array in input order, including duplicates. An empty result publishes [], and unchanged arrays do not notify consumers again. Loading or processing failures retain the last successful value. Missing fields, non-scalar values, and non-finite numbers cause an error.

Output parameters cannot be changed through the embedding API and are not saved in bookmarks. They can drive scale and visual expressions. Outputs used by another data source or transform follow ordinary reactive updates; there is no special ordering guarantee between independent pipelines.

Avoid feeding an output back into the pipeline that produces it. Feedback can also pass through a lazy source's domain or layout, causing repeated loading or unstable updates. GenomeSpy does not detect these cycles.

Loading precomputed values

A normal eager source can load a table of cuts:

{
  "data": {
    "url": "cuts.csv",
    "format": { "parse": { "cut": "number" } }
  },
  "transform": [{ "type": "values", "field": "cut", "param": "cuts" }],
  "scales": {
    "x": {
      "type": "index",
      "domain": [0, 5],
      "gapPositions": { "expr": "cuts" },
      "gapWidth": 10
    }
  }
}

The input table could contain cut values 2 and 4. The scale then inserts gaps before indices 2 and 4. Sorting and deduplication of gap positions are scale behavior; "values" itself preserves the prepared rows' order and duplicates.

Inline, named, sequence, and lazy inputs can also publish outputs. Lazy outputs describe the last successfully loaded window, which may include prefetched data or summary bins.

Deriving cuts in a branch

A "branch" can filter or summarize metadata for the output while the main pipeline keeps all rows. In this synthetic heatmap, a slider controls which cluster boundaries become gaps. All 18 cells remain visible.

{
  "description": [
    "Metadata-derived heatmap gaps",
    "A branch publishes cluster boundaries without filtering heatmap cells. The slider selects boundaries by strength.",
    "Data source: synthetic metadata and values."
  ],

  "params": [
    {
      "name": "minimumStrength",
      "value": 0,
      "bind": {
        "name": "Minimum boundary strength",
        "input": "range",
        "min": 0,
        "max": 1,
        "step": 0.1
      }
    }
  ],

  "data": {
    "values": [
      { "column": 0, "cluster": "A", "strength": 0 },
      { "column": 1, "cluster": "A", "strength": 0 },
      { "column": 2, "cluster": "B", "strength": 0.4 },
      { "column": 3, "cluster": "B", "strength": 0 },
      { "column": 4, "cluster": "C", "strength": 0.9 },
      { "column": 5, "cluster": "C", "strength": 0 }
    ]
  },

  "transform": [
    { "type": "collect", "sort": { "field": "column" } },
    {
      "type": "branch",
      "transform": [
        {
          "type": "window",
          "sort": { "field": "column" },
          "ops": ["lag", "row_number"],
          "fields": ["cluster", null],
          "as": ["previousCluster", "rank"]
        },
        {
          "type": "filter",
          "expr": "datum.rank > 1 && datum.cluster !== datum.previousCluster && datum.strength >= minimumStrength"
        },
        { "type": "values", "field": "column", "param": "cuts" }
      ]
    },
    {
      "type": "cross",
      "from": {
        "data": { "values": [{ "row": 0 }, { "row": 1 }, { "row": 2 }] }
      }
    },
    {
      "type": "formula",
      "expr": "sin(datum.column * 0.9 + datum.row)",
      "as": "expression"
    }
  ],

  "width": 360,
  "height": 120,

  "mark": "rect",

  "encoding": {
    "x": {
      "field": "column",
      "type": "index",
      "scale": {
        "domain": [0, 5],
        "gapPositions": { "expr": "cuts" },
        "gapWidth": 10,
        "paddingInner": 0.03
      },
      "axis": { "title": "Feature", "tickMinStep": 1 }
    },
    "y": {
      "field": "row",
      "type": "index",
      "scale": { "domain": [0, 2], "reverse": true, "paddingInner": 0.03 },
      "axis": { "title": "Sample", "tickMinStep": 1 }
    },
    "color": {
      "field": "expression",
      "type": "quantitative",
      "scale": { "domain": [-1, 1], "scheme": "blueorange" },
      "legend": null
    }
  }
}

The branch uses window to compare adjacent cluster labels, then filters boundaries by strength. The upstream collect caches metadata so changing the ordinary minimumStrength parameter replays the branch without reloading data.