Skip to content
John M. Baughman edited this page Dec 13, 2018 · 14 revisions

Marble diagrams is a way of visually representing reactive (asynchronous) data streams.

In a marble diagram, the X axis (left to right) represents time. Each horizontal line on the Y axis represents a subscription to a source sequence. These are usually named “ws”, “xs”, “ys”; as in “the values of the x source” or “x’es”. “zs” is usually reserved for the output sequence.

The start of a solid line represents the start of a subscription, which can be caused by the user subscribing (which is often the case) or in a situation where the operator causes another subscription to be begin (see Example 2: Concat).

The end of a solid line represents the end of a subscription. This can be caused by completion, errors or for a reason specific to that operator.

The bottom-most line always represents the output of the sequence. That is, what your code will see if you subscribe to the operator in question. Sometimes there is only one line (interval). When there is two (skip, take) the upper lines represent the input to the operator.

Vertical lines that go from a higher sequence to the bottom sequence represent the value from a source sequence causing a value on the output sequence. See take for an example.

On a subscription line, various symbols are used to represent the output of a sequence:

  • “o” represents a value (a call to IObserver.onNext). This value will be an instance of the type returned by the type property of raix.reactive.IObservable.
  • “/” represents a completion (IObserver.onCompleted).
  • “x” represents an error (IObserver.onError)

If there is a label below a symbol (usually “o”), it can mean several things:

  • Usually it represents the value of “o” (see Example 1)
  • If the label looks like a function call, like “f(x)”, it means that the value from “xs” is passed to the function “f”. A legend will appear above these marble diagrams to explain each function.

Example 1: Range

────o──────────o──────────o────────────────o──/
  start     start+1    start+2  ...   start+count-1

Above is the marble diagram for the range operator. As it has only one line, we can presume that this operator has output but does not use any other sequences as input.

We can also see that this sequence will emit values (by calling the observer’s onNext), starting at start (an argument for range). It will increment the value before calling onNext again, and will continue until it reaches “start + count – 1”, at which time it will complete (by calling observer’s onCompleted).

Example 2: Concat

The concat operator actually contains two marble diagrams, which is not uncommon. The first shows the optimal case, and the second shows an error case. We’ll look at them one at a time.

ws ─o──o──/
    │  │  │
xs  │  │  └─o──o──o──/
    │  │    │  │  │  │
ys  │  │    │  │  │  └───o───o──o──/
    │  │    │  │  │      │   │  │  │
zs ─o──o────o──o──o──────o───o──o──/

We can see that this operator accepts multiple input sequences, with this sample diagram shows 3 inputs: ws, xs and ys.

When the concat sequence is subscribed to, the operator will subscribe to ws (the first operator). ws emits three values and then completes. Each of the three values are sent to _concat_’s output, meaning that the subscriber to concat will receive them in onNext. After ws completes concat will subscribe to xs, the next input sequence. This process will continue until the last sequence, ys, completes, at which point zs will complete.

ws ─o──o──/
    │  │  │
xs  │  │  └─o──o──x
    │  │    │  │  │
ys  │  │    │  │  │
    │  │    │  │  │
zs ─o──o────o──o──x

The second diagram shows concat running normally until a sequence, xs in this case_, emits an error. concat responds by emitting the error and halting the sequence, unsubscribing from the active sequence and not subscribing to any more.

This response to an error is common, but not ubiquitous. Some operators like catchError, retry, and onErrorResumeNext swallow errors and do other things.

Example 3: Zip

xs = source
ys = other
zs = output
f = selector

xs ──o────────────────o────────────
     └──┐   ┌─────────┤
        │   │         │
ys ─────o───o──o──────│──────o────/
        │             │           │
      f(x,y)        f(x,y)        │
zs ─────o─────────────o───────────/

The above diagram shows us that values are not emitted from zs until a value from both xs and ys have been emitted. Furthermore, values are only counted once and are used in a first-in-first-served manner (like a zipper).

When values from both sources are available, they are passed to the selector f and the output of f is sent to subscribers of the zip sequence.

xs ──o────────x
     └──┐     │
        │     │
ys ─────o──o──│
        │     │
      f(x,y)  │
zs ─────o──o──x

This diagram shows that an error from one source sequence (xs) will cause other source sequqences to be unsubscribed from and the error passed to the observer.

Clone this wiki locally